2316043
Practical -1
Aim:
Sort a given set of elements using Insertion sort method and determine the time required to sort
the elements. Plot the graph of the time taken versus n. The elements can be generated using
random number generator.
Pseudo Code for Insertion Sort:
Insertion-Sort (A)
for j = 2 to A.length()
key = A[ j ]
// Insert A[ j ] into the sorted sequence A[ 1….j-1 ]
i = j -1
while i > 0 and A[ i ] > key
A [ i +1 ] = A [ i ]
i=i–1
A[ i + 1 ] = key
Code:
import time
import numpy.random as np
import matplotlib.pyplot as plt
list1=[]
def insertion_sort(A):
for j in range(2,len(A)):
key=A[j]
i=j-1
while i>-1 and A[i]>key:
A[i+1]=A[i]
1
2316043
i=i-1
A[i+1]=key
list2=[]
for i in range(5,0,-1):
A=np.randint(i, 900,i*1000)
list1.append(len(A))
Starttime=time.clock()
insertion_sort(A)
endtime=time.clock()
list2.append(endtime - Starttime)
plt.plot(list1,list2)
print(list1, "\n", list2)
Output:
[5000, 4000, 3000, 2000, 1000]
[4.181740838952919, 2.6366416284978413, 1.5231062973732605,
0.6836392882361224, 0.16351879782666856]
2
2316043
Practical – 2
Aim:
Sort a given set of elements using Merge sort and determine the time required to sort the
elements. Plot a graph of time taken versus n. The elements can be generated random number
generator.
Pseudo Code for Merge Sort:
Merge ( A , p , q , r )
n1 q – p +1
n2 r – q
create arrays L [ 1..…n1+1] & R [ 1……n2+1]
for i 1 to n1
do L[ i ] A[ p + i -1 ]
for j 1 to n2
do R[ j ] A[ q + p]
L[ n1 + 1] ∞
R[ n2 + 1] ∞
I1
J1
for k p to r
do if L[ i ] <= R[ j ]
A[ k ] L [ i ]
i i+1
else
A[k] R[j]
Jj+1
Merge_Sort ( A , p , r )
if p < r then
q L [ ( p + (r -1))//2 ]
Merge_Sort ( A , p, q )
3
2316043
Merge_Sort ( A , q+1 , r )
Merge ( A , p , q , r )
Code:
import time
import numpy.random as np
import matplotlib.pyplot as plt
list1=[]
def merge(arr,p,q,r):
n1=q-p+1
n2=r-q
L=[0]*(n1)
R=[0]*(n2)
for i in range(0,n1):
L[i]=arr[p+i]
for j in range(0,n2):
R[j]=arr[q+1+j]
i=0
j=0
k=1
while(i<n1 and j<n2):
if(L[i]<=R[j]):
arr[k]=L[i]
i+=1
else:
arr[k]=R[j]
j+=1
4
2316043
k+=1
while i<n1:
arr[k]=L[i]
i+=1
k+=1
while j<n2:
arr[k]=R[j]
j+=1
k+=1
def mergeSort(arr,p,r):
if p<r:
q=(p+(r-1))//2
mergeSort(arr,p,q)
mergeSort(arr,q+1,r)
merge(arr,p,q,r)
list2=[]
for i in range(0,5,1):
arr=np.randint(i, 900,i*1000)
list1.append(len(arr))
Starttime=time.clock()
mergeSort(arr,0,len(arr)-1)
endtime=time.clock()
list2.append(endtime - Starttime)
plt.plot(list1,list2)
print(list1, "\n", list2)
5
2316043
Output:
[0, 1000, 2000, 3000, 4000]
[2.133328784736932e-06, 0.00887720772862366, 0.01926822556112029,
0.03390627433328319, 0.057405744201076914]
--
6
2316043
Practical – 3
Aim:
Sort a given set of elements using Quick sort and determine the time required to sort the
elements. Plot a graph of time taken versus n. The elements can be generated random number
generator.
Algorithm:
Quick_Sort ( A, p , r )
if p < r then
q Partition ( A , p , r )
Quick_Sort ( A , p , q-1 )
Quick_Sort ( A , q+1 , r )
Partition ( A , p , r )
XA[r]
i p -1
for j p to r-1
do if A[ j ] <= x then
i i +1
Exchange A[ i ] A[ j ]
Exchange A[ i+1 ] A[ r ]
return i+1
Code:
import time
import numpy.random as np
import matplotlib.pyplot as plt
list1=[]
def partition(A,p,r):
7
2316043
pivot=A[r]
i=p-1
for j in range(p,r):
if A[j]<=pivot:
i=i+1
A[i],A[j]=A[j],A[i]
A[i+1],A[r]=A[r],A[i+1]
return (i+1)
def quicksort(A,p,r):
if p<r:
q=partition(A,p,r)
quicksort(A,p,q-1)
quicksort(A,q+1,r)
list2=[]
A = None
for i in range(5,0,-1):
A=np.randint(i, 900,i*1000)
list1.append(len(A))
Starttime=time.clock()
quicksort(A,0,len(A)-1)
endtime=time.clock()
list2.append(endtime - Starttime)
plt.plot(list1,list2)
print(list1, "\n", list2)
8
2316043
Output:
[5000, 4000, 3000, 2000, 1000]
[0.04519831225363902, 0.03296019772623282, 0.024484453939617623,
0.013818320519646932, 0.006184599314458694]
9
2316043
Practical – 4
Aim:
Sort a given set of elements using Heap sort and determine the time required to sort the elements.
Plot a graph of time taken versus n. The elements can be generated random number generator
Algorithm:
MaxHeapify(A, i)
l left(i) # left(i) = 2*i + 1
r right(i) #right(i)=2*i +2
if l <= heap-size[A] and A[l] > A[i]
then largest l
else largest i
if r <= heap-size[A] and A[r] > A[largest]
then largest r
if largest != i
then swap A[i] with A[largest]
MaxHeapify(A, largest)
end function
BuildMaxHeap(A)
heap-size[A] length[A]
for i length[A] to -1
do MaxHeapify(A, i)
end function
HeapSort(A)
BuildMaxHeap(A)
10
2316043
for i = length[A]-1 to 0
do swap A[1] with A[i]
heap-size[A] = heap-size[A] – 1
MaxHeapify(A, 1)
end function
Code:
import numpy.random as np
import matplotlib.pyplot as plt
import time
class Heap:
def __init__(self):
self.arr = None
self.size = None
def left(i):
return 2*i+1
def right(i):
return 2*i+2
def max_heapify(heap,i):
L=left(i)
R=right(i)
#print(i)
if L < heap.size and heap.arr[L]>heap.arr[i]:
largest=L
else:
largest=i
11
2316043
if R < heap.size and heap.arr[R]>heap.arr[largest]:
largest=R
if largest != i:
heap.arr[i],heap.arr[largest]=heap.arr[largest],heap.arr[i]
max_heapify(heap,largest)
def build_max_heap(heap):
for i in range(heap.size//2,-1,-1):
max_heapify(heap,i)
def heap_sort(heap):
build_max_heap(heap)
for i in range(heap.size-1,0,-1):
heap.arr[i],heap.arr[0] = heap.arr[0],heap.arr[i]
heap.size -= 1
max_heapify(heap,0)
list2=[]
list1 = []
for i in range(5,0,-1):
heap = Heap()
heap.arr = np.randint(i, 800,50*i)
heap.size = len(heap.arr)
list1.append(heap.size)
Starttime=time.clock()
heap_sort(heap)
endtime=time.clock()
list2.append(endtime - Starttime)
12
2316043
print(list1)
print(list2)
plt.plot(list1,list2)
plt.show()
Output:
13
2316043
Practical-5
Aim:
a. Print all the nodes reachable from a given starting node in a digraph using BFS method.
b. Check whether a given graph is connected or not using DFS method.
Algorithm:
bfs(graph, root)
visited, queue list(), deque([root])
visited.append(root)
while queue
vertex queue.popleft()
for neighbour in nx.neighbors(graph, vertex):
if neighbor is not visited
visited.append(neighbour)
queue.append(neighbour)
return visited
dfs(graph,root,visited = None)
if visited == None
visited list()
visited.append(root)
for neighbor in nx.neighbors(graph, root)
if neighbor is not visited
dfs(graph,neighbor,visited)
return visited
14
2316043
Code:
import networkx as nx
from matplotlib import pyplot as plt
from _collections import deque
def bfs(graph, root):
visited, queue = list(), deque([root])
visited.append(root)
while queue:
vertex = queue.popleft()
for neighbour in nx.neighbors(graph, vertex):
if neighbour not in visited:
visited.append(neighbour)
queue.append(neighbour)
return visited
def dfs(graph,root,visited = None):
if visited == None:
visited = list()
visited.append(root)
for neighbor in nx.neighbors(graph, root):
if neighbor not in visited:
dfs(graph,neighbor,visited)
return visited
G = nx.DiGraph()
G.add_edge(5,1)
15
2316043
G.add_edge(1,2)
G.add_edge(2,3)
G.add_edge(2,5)
G.add_edge(5,3)
G.add_edge(2,1)
G.add_edge(3,4)
nx.draw_networkx(G)
plt.show()
print("BFS : " ,bfs(G, 1))
print("DFS : " ,dfs(G, 1))
Output:
16
2316043
Practical – 6
Aim:
To implement Longest Common Subsequence.
Algorithm:
lcs(X,Y,m,n)
if(m==0 or n==0)
return (0)
elif(X[m-1]==Y[n-1])
return (1+lcs(X,Y,m-1,n-1))
else:
return max(lcs(X,Y,m,n-1),lcs(X,Y,m-1,n))
Code:
def lcs(X,Y,m,n):
if(m==0 or n==0):
return (0)
elif(X[m-1]==Y[n-1]):
return (1+lcs(X,Y,m-1,n-1))
else:
return max(lcs(X,Y,m,n-1),lcs(X,Y,m-1,n))
X="knowledge"
Y="pledge"
print("length of lcs is ",lcs(X,Y,len(X),len(Y)))
Output:
17
2316043
Practical – 7
Aim:
Compute the transitive closure of a given directed graph using Warshall's algorithm.
Algorithm:
for k 1 to n do
for I 1 to n do
for j 1 to n do
R(k-1)[i,j] R(k-1) [i,j] or (R(k-1) [i,k] and R(k-1) [k,j])
return R(n)
Code:
import networkx as nx
from matplotlib import pyplot as plt
def printSolution(reach):
ll=[]
print ("Following matrix transitive closure of the given graph ")
for i in range(4):
l=[]
for j in range(4):
l.append(reach[i][j])
ll.append(l)
for row in ll:
for elem in row:
print(elem, end=' ')
print()
def transitiveClosure(graph):
reach =[i[:] for i in graph]
18
2316043
for k in range(4):
for i in range(4):
for j in range(4):
reach[i][j] = reach[i][j] or (reach[i][k] and reach[k]
[j])
printSolution(reach)
G = nx.DiGraph()
G.add_edge(1,1)
G.add_edge(1,2)
G.add_edge(1,3)
G.add_edge(2,2)
G.add_edge(2,3)
G.add_edge(3,3)
G.add_edge(3,4)
G.add_edge(4,4)
G.add_edge(4,3)
nx.draw_networkx(G)
plt.show()
graph = [[1, 1, 0, 1],
[0, 1, 1, 0],
[0, 0, 1, 1],
[0, 0, 1, 1]]
transitiveClosure(graph)
19
2316043
Output:
20
2316043
Practical – 8
Aim:
Implement N Queen's problem using Back Tracking.
Algorithm:
1. Create a class QueenChessBoard.
2. The board configuration is stored in a list called columns where if c = columns[r], then a queen is at
column c and row r.
3. The class provides methods to place a queen in the next row given the column, remove the queen from
the last row where a queen is present, check whether a given column is safe to place a queen in the next
free row and to display the board.
4. Define the function solve_queen that takes the size of the board as argument.
5. It creates a QueenChessBoard object with that size.
6. A while loop is created with condition True.
7. In each iteration, it is checked to see in which column a queen can be placed in the first available row
of the board. This is done using another loop inside the while loop.
8. If such a column is there, the queen is placed there and the inner loop is stopped.
9. After the inner loop, if a column was not found or the board became full, then we need to backtrack.
10. If the board is full, then before backtracking, we need to display the board.
11. Backtracking is done by removing the queen from the last filled row and making the next iteration try
column values above the column values of the queen just removed.
12. If it is found that no more queens can be removed, then we are finished.
13. The number of solutions is kept track of and printed at the end.
Code:
class QueenChessBoard:
def __init__(self, size):
self.size = size
self.columns = []
def place_in_next_row(self, column):
self.columns.append(column)
def remove_in_current_row(self):
21
2316043
return self.columns.pop()
def is_this_column_safe_in_next_row(self, column):
row = len(self.columns)
for queen_column in self.columns:
if column == queen_column:
return False
for queen_row, queen_column in enumerate(self.columns):
if queen_column - queen_row == column - row:
return False
for queen_row, queen_column in enumerate(self.columns):
if ((self.size - queen_column) - queen_row
== (self.size - column) - row):
return False
return True
def display(self):
for row in range(self.size):
for column in range(self.size):
if column == self.columns[row]:
print('Q', end=' ')
else:
print('.', end=' ')
print()
def solve_queen(size):
board = QueenChessBoard(size)
number_of_solutions = 0
row = 0
22
2316043
column = 0
while True:
while column < size:
if board.is_this_column_safe_in_next_row(column):
board.place_in_next_row(column)
row += 1
column = 0
break
else:
column += 1
if (column == size or row == size):
if row == size:
board.display()
print()
number_of_solutions += 1
board.remove_in_current_row()
row -= 1
try:
prev_column = board.remove_in_current_row()
except IndexError:
break
row -= 1
column = 1 + prev_column
print('Number of solutions:', number_of_solutions)
n = int(input('Enter n: '))
solve_queen(n)
23
2316043
Output:
24
2316043
Practical – 9
Aim:
Obtain the Topological ordering of vertices in a given digraph
Algorithm:
Steps involved in finding the topological ordering of a DAG:
Step-1: Compute in-degree (number of incoming edges) for each of the vertex present in the DAG
and initialize the count of visited nodes as 0.
Step-2: Pick all the vertices with in-degree as 0 and add them into a queue (Enqueue operation)
Step-3: Remove a vertex from the queue (Dequeue operation) and then.
1. Increment count of visited nodes by 1.
2. Decrease in-degree by 1 for all its neighboring nodes.
3. If in-degree of a neighboring nodes is reduced to zero, then add it to the queue.
Step 5: Repeat Step 3 until the queue is empty.
Step 5: If count of visited nodes is not equal to the number of nodes in the graph then the topological
sort is not possible for the given graph.
Code:
import networkx as nx
from matplotlib import pyplot as plt
def TopologicalSort(graph):
TopologicalSortedList = []
ZeroInDegreeVertexList = []
inDegree = { u : 0 for u in graph }
for u in graph:
for v in graph[u]:
inDegree[v] += 1
for k in inDegree:
if (inDegree[k] == 0):
ZeroInDegreeVertexList.append(k)
while ZeroInDegreeVertexList:
v = ZeroInDegreeVertexList.pop(0)
TopologicalSortedList.append(v)
25
2316043
for neighbour in graph[v]:
inDegree[neighbour] -= 1
if (inDegree[neighbour] == 0):
ZeroInDegreeVertexList.append(neighbour)
return TopologicalSortedList
graph = {
'1': set(['2','3']),
'2': set(['4']),
'3': set(['2','4']),
'4': set([])
G = nx.DiGraph()
G.add_edge(1,2)
G.add_edge(2,4)
G.add_edge(1,3)
G.add_edge(3,2)
G.add_edge(3,4)
nx.draw_networkx(G)
plt.show()
result = TopologicalSort(graph)
print("Topological sort >>> ", result)
if (len(result) == len(graph)):
print("Directed Acyclic Graph!")
else:
print("Graph has cycles!")
26
2316043
Output
27
2316043
Practical – 9
Aim:
To implement Huffman coding using Greedy algo.
Algorithm:
HUFFMAN(C)
n ← |C|
Q←C
for i 1 to n - 1
do allocate a new node z
left[z] ← x ← EXTRACT-MIN (Q)
right[z] ← y ← EXTRACT-MIN (Q)
f [z] ← f [x] + f [y]
INSERT(Q, z)
return EXTRACT-MIN(Q)
Code:
import heapq
from collections import defaultdict
def encode(frequency):
heap = [[weight, [symbol, '']] for symbol, weight in
frequency.items()]
heapq.heapify(heap)
while len(heap) > 1:
lo = heapq.heappop(heap)
hi = heapq.heappop(heap)
for pair in lo[1:]:
pair[1] = '0' + pair[1]
for pair in hi[1:]:
28
2316043
pair[1] = '1' + pair[1]
heapq.heappush(heap, [lo[0] + hi[0]] + lo[1:] + hi[1:])
return sorted(heapq.heappop(heap)[1:], key=lambda p: (len(p[-1]),
p))
data = "SHIVANI"
frequency = defaultdict(int)
for symbol in data:
frequency[symbol] += 1
huff = encode(frequency)
print ("Symbol".ljust(10) + "Weight".ljust(10) + "Huffman Code")
for p in huff:
print (p[0].ljust(10) + str(frequency[p[0]]).ljust(10) + p[1])
Output:
29