0% found this document useful (0 votes)
19 views233 pages

Dsa Observation

Uploaded by

muguntharajank
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
19 views233 pages

Dsa Observation

Uploaded by

muguntharajank
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 233

2024-2025

2024-2025
INDEX
SI DATE EXPERIMENT PAGE SIGNATURE
NO NO
Ex.no.1
Date: Implement Simple ADT as Python classes

Aim:
To write a python program to implement Simple ADT as python classes.

Algorithm:

Step 1: Start
Step 2: Create a class called student with name, register_no as data members
Step 3: Define function Str to print data members in class student
Step 4: Define function length to get len of object
Step 5: Create a object to the class student
Step 6: Print name , reg.no and marks
OUTPUT:
Program:
Class student:
def init (self,n,rg,m):
self.name=n
self.reg_no=rg
self.marks=m
def str (self):
return f“name={self._name}reg_no={self.reg_no}
marks={self._marks}”
def len (self):
return(len(self.marks))
def add (self.other):
return(other.g)
ob1 = student(“kohli”, 18, [23,34,56,78])
ob2=student(“Dhoni”,7,[45,12]) print(str(ob1))
print(len(ob1))
print(str(ob2))
print(len(ob2))

Result:
Thus, the above program runs successfully and the Output was verified.
Ex.no.2
Date: Implement Recursive Algorithm in python

Aim:
To write a python program to implement recursive algorithms in python of
factorial.

Algorithm:

Step 1: Start
Step 2: Define function factorial of n.
Step 3: Using if condition, if n equals to zero and then return.
Step 4: else return n multiplies of factorial of n-1.
Step 5: Creating a object in the name of fact, to the function factorial
Step 6: Print fact
Step 7: Stop
OUTPUT:
Program:
def factorial(n):
if(n==0):
return 1
else:
return(n*factorial(n-1))
fact = factorial(3)
print(“Factorial is:”,fact)

Result:
Thus, the above program runs successfully and the Output was verified.
Ex.no.3
Date: Implement list ADT using python arrays

Aim:
To write a python program to implement list ADT using python arrays to
append an remove values.

Algorithm:
display operation
Input: none
Output: print the elements in the linked list
Algorithm display()
{
step 1: start
step 2: print the list
step 3: stop
}
append operation
Input: element e to be appended at the end of the array
Output: none
Algorithm append()
{
step 1: start
step 2: if array size == capacity
then resize the array and the append the new value e
step 3: append the new value e at the end of the list
step 4: increment size of the list by 1
step 5: stop
}
insert operation
Input: value - to be inserted at first or middle or last position in the array
k – key after which value has to be inserted
Output: none

Algorithm insert()
{
step 1: start
step 2: if array size == capacity
then resize the array and the append the new value e
step 3: for i in range of index_of_the_last_element down to k+1
self._A[i] = self._A[i-1]
self._A[k] = value
step 4: store the value after k
step 4: increment size of the list by 1
step 5: stop
}
remove operation
Input: value - to be removed from the array
Output: message „value not in the list‟ if the value is not present in the array list.
Algorithm remove()
{
step 1: start
step 2: for i in range 1 to n
if self._A[i] == value:
for j in range from i to n-1
self._A[j] = self._A[j+1]
return end if
end for
step 3: print „value not in the list. Hence, it cannot be removed‟.
step 4: stop
}
Program:
class ArrayList:
def init (self): self._n
= 0 self._capacity
= 1 self._A =
[None]
def display(self):
print(“len of _A is : “, len(self._A))
print(“A is : “, *self._A)

def append(self,obj):
if self._n == self._capacity:
self._resize(2*self._capacity)
self._A[self._n] = obj
self._n = self._n + 1
def _resize(self, c):
B = [None]*c
self._capacity = c
print(“self._capacity is : “, self._capacity)
for k in range(0, self._n, 1):
B[k] = self._A[k]
self._A = B
def insert(self, k, value):
if self._n == self._capacity:
self.resize(2*self._capacity)
for i in range(self._n, k, -1):
self._A[i] = self._A[i-1]
self._A[k] = value
self._n = self._n + 1
def remove(self, value):
for i in range(0, self._n, 1):
if self._A[i] == value:
for j in range(i, self._n, 1):
self._A[j] = self._A[j+1]
return
print(“Value not in the list. It cannot be removed”)
def pop(self, index):
if index >=self._n:
print(“Index greater than size of array. It cannot be removed”)
for j in range(index, self._n-1, 1):
self._A[j] = self._A[j+1]
return
obj1 = ArrayList()
obj1.display()
print()
obj1.append(10)
obj1.display()
print()
obj1.append(20)
obj1.display()
print()
obj1.append(30)
obj1.display()
Output
obj1.append(40)
obj1.display()
print()
obj1.append(50)
obj1.display()
print()
obj1.append(60)
obj1.display()
print()
obj1.append(70)
obj1.display()
print()
obj1.append(80)
obj1.display()
print()
obj1.append(90)
obj1.display()
print()
obj1.append(100)
obj1.display()
print()
obj1.append(110)
obj1.display()
print()
obj1.insert(3, 35)
obj1.display()
print()
obj1.insert(5, 45)
obj1.display()
print()
obj1.remove(10)
obj1.display()
print()
obj1.remove(35)
obj1.display()
print()
obj1.remove(1000)
obj1.pop(2)
obj1.display()
print()

Result:
Thus, the above program runs successfully and the Output was verified.
Ex.no.4
Date: Linked List Implementation of Lists

Aim:
To write a python program to implement a linked list implementation of List

Algorithm:
display operation
Input: none
Output: print the elements in the linked list
Algorithm display()
{
step 1: start
step 2: temp = head
step 2: while temp != None
print temp node‟s element value
temp = temp._next
step 4: stop
}
insert_first operation
Input: element to insert as first into the list
Output: none
Algorithm insert_first(e)
{
step 1: start
step 2: create a node new node and make it value as e and next as None
step 3: if list is empty
then make the head pointer and tail pointer point to new node and
return
else goto step 4
step 4: newnode._next = head
step 5: head = newnode
step 6: stop
}

insert_last operation
Input: element to insert as first into the list
Output: none
Algorithm insert_last(e)
{
step 1: start
step 2: create a node new node and make it value as e and next as None
step 3: if self._tail == None:
self._tail = newnode
self._head = newnode
else:
self._tail._next = newnode
self._tail = newnode
step 4: stop
}
remove_first operation
Input: none
Output: none
Algorithm remove_first(e)
{
step 1: start
step 2: if list is empty:
print (“List already empty. So, remove not needed”)
elif only one element is in the list:
self._head = self._head._next or None
self._tail = None
else:
self._head = self._head._next
step 3: stop
}
remove_first operation
Input: none
Output: none
Algorithm remove_last(e)
{
step 1: start
step 2: # case 1: list is empty
if self._tail == None:
print (“List already empty. So, remove not needed”)
# case 2: Only one element in the list
# we have to adjust both tail and head pointers here.
elif self._head._next == None:
print(self._tail._element)
self._head = self._head._next or None
self._tail = None
#case 3: list has more than one node
else:
set temp = self._head
while temp._next != self._tail:
temp = temp._next
# Here, we can remove temp._next._element or tail._element
# Both are same only.
print(“Last element remove is: “, temp._next._element)
temp._next = None
self._tail = temp
step 3: stop
}
remove_middle operation
Input: none
Output: none
Algorithm remove_middle(e)
{
step 1: start
step 2: Traverse to the node before the node to be deleted.
Step 3: Now, temp points to the the node which is before the node to be
deleted. Make it point to the next of the node to be deleted.
step 4: stop
}
insert_middle operation
Input: e – element to be inserted into the new node‟s value
After – insertion of the new node happens after this node
Output: none
Algorithm insert_middle(e)
{
step 1: start
step 2: create a node new node and make it value as e and next as None
step 3: temp = self._head
step 4: Traverse the list till temp != None
if (temp._element == after):
new_node._next = temp._next
temp._next = new_node
return
else
temp = temp._next
step 4: stop
}
Program:
class LinkedList:
class _Node:
def init (self, e, n):
self._element = e
self._next = n
def init (self):
self._head = None
self._tail = None;
def display(self):
temp = self._head
output = “List is: “
if temp == None:
print(output + “ empty”)
return
while(temp != None):
#print(“inside while of display”)
output = output + “->” + str(temp._element)
temp = temp._next
print(output)
def insert_first(self, value):
newnode = self._Node(value, None)
if self._head == None:
self._head = newnode
self._tail = newnode
else:
newnode._next = self._head
self._head = newnode

def insert_last(self, value):


newnode = self._Node(value, None)
if self._tail == None:
self._tail = newnode
self._head = newnode
else:
self._tail._next = newnode
self._tail = newnode
def remove_first(self):
if self._head == None:
print (“List already empty. So, remove not needed”)
elif self._head._next == None:
print(“First element remove is:
{0}”.format(self._head._element))
self._head = self._head._next
self._tail = None
else:
print(“First element remove is:
{0}”.format(self._head._element))
self._head = self._head._next
def remove_last(self):
# case 1: list is empty
if self._tail == None:
print (“List already empty. So, remove not needed”)
# case 2: Only one element in the list
# we have to adjust both tail and head pointers here.
elif self._head._next == None:
print(“Last element remove is:
{0}”.format(self._tail._element))
self._head = self._head._next
self._tail = None
#case 3: list has more than one node
else:
temp = self._head
while temp._next != self._tail:
temp = temp._next
# Here, we can remove temp._next._element or tail._element
# Both are same only.

print(“Last element remove is:


{0}”.format(temp._next._element))
#self._head = self._head._next
temp._next = None
self._tail = temp
def remove_middle(self, val):
if self._head._element == val:
self.remove_first()
return
if self._tail._element == val:
self.remove_last()
return
# Step 1: Traverse to the node before the node to be deleted.
temp = self._head
Output:
while temp._next._element != val:
temp = temp._next
# Step 2: Now, temp points to the a node which is before the node to be
deleted.
# Make it point to the node next to the node to be deleted.
# The unused node will be automatically garbage collected by the PRE
print(“Middle element remove is:
{0}”.format(temp._next._element))
temp._next = temp._next._next
def insert_middle(self,after, e):
new_node = self._Node(e, None)
temp = self._head
while(temp != None):
print(“inside while: “, temp._element)
if (temp._element == after):
new_node._next = temp._next
temp._next = new_node
return
temp = temp._next
print(“The element {0} is not in the list”.format(after))
obj1 = LinkedList()
#obj1.display()
obj1.insert_first(100)
#obj1.display()
obj1.insert_first(200)
#obj1.display()
obj1.insert_first(300)
obj1.display()
obj1.insert_last(50)
obj1.display()
obj1.insert_last(25)
obj1.display()
“““obj1.remove_first()
obj1.remove_first()
obj1.remove_first()
obj1.remove_first()
obj1.remove_first()
obj1.remove_first()
obj1.remove_first()”““
“““
obj1.display()
obj1.remove_last()
obj1.remove_last()
obj1.remove_last()
obj1.remove_last()”““
“““
obj1.display()
obj1.remove_middle(100)
obj1.remove_middle(200)
obj1.display()
obj1.remove_middle(300)
obj1.display()

Result:
Thus, the above program runs successfully and the Output was verified.
Ex.no.5.a
Date: Implementation of Stack ADT

Aim:
To write a python program to implement the Stack data structure and to
demonstrate its various operations

Algorithm:
push operation
Input: element
Output: none
Algorithm push(e)
{
step 1: append element e to the end of the stack and return
step 2: stop
}
pop operation
Input: none
Output: popped element from the stack
Algorithm pop()
{
step 1: if call to is_empty() returns True
then print „stack is empty‟ and return
else pop the top element in the stack and return
step 2: stop
}
Top operation
Input: none
Output: integer value which is in the top element of the stack
Algorithm top()
{
step 1: if call to is_empty() returns True
then print „stack is empty‟ and return
else just return the top element in the stack without removing it
step 2: stop
}
Is_empty() operation
Input: none
Output: True of False
Algorithm is_empty()
{
step 1: if length of stack object equals 0
then return the Boolean value True
else return the Boolean value False
step 2: stop
}
len() operation
Input: none
Output: an integer value representing the number of items in the present stack
object
Algorithm is_empty()
{
step 1: return length of the stack array which is present inside the stack
object
step 2: stop
}
Program:
class ArrayStack: # class definition for a stack
def init (self):
self._data = []
def len (self):
return len(self._data)
def is_empty(self):
return len(self._data) == 0
def push(self, e):
self._data.append(e)
def top(self):
if self.is_empty() == True:
print(“stack is empty”)
else:
return self._data[-1]
def pop(self):
if self.is_empty() == True:
print(“stack is empty”)
else:
return self._data.pop()
# Create the ArrrayStack object now
obj1 = ArrayStack()
# len is 0 and top of stack is None
print(“length of stack is: “, len(obj1))
print(“top of stack is: “, obj1.top())
# push three elements and print len() and top() of stack
obj1.push(100)
obj1.push(200)
OUTPUT:
obj1.push(300)
print(“\n”, “length of stack is: “, len(obj1))
print(“top of stack is: “, obj1.top())
# pop an element and print it.
print(“\n”)
print(“popped element is: “, obj1.pop())
# print current top of stack
print(“\n”)
print(“top of stack is: “, obj1.top())

Result:
Thus, the above program runs successfully and the Output was verified.
Ex.no.5.b
Date: Implementation of Queue ADT

Aim:
To write a python program to implement the Queue data structure and to
demonstrate its various operations
Algorithm:
deque operation
Input: none
Output: element at the front of the queue or „queue is empty‟ message
Algorithm dequeue(e)
{
step 1: start
step 2: if queue is empty
then print „queue is empty. Dequeue not possible‟ and return
else goto step 3
step 3: answer = front element in the stack
make the content of front position as none
increment front pointer to next position
decrement size of the queue by 1
return answer
step 4: stop
}
enqueue operation
Input: element to insert into the queue
Output: none
Algorithm dequeue(e)
{
step 1: start
step 2: if queue is full
then resize the stack and goto step 3
else goto step 3
step 3: find the next available position in the queue
insert the new element e in the next available position
increment size of the queue by 1
step 4: stop
}

front operation
Input: none
Output: element at the front of the queue or „queue is empty‟ message
Algorithm front(e)
{
step 1: start
step 2: if queue is empty
then print „queue is empty‟ and return
else return the front element without removing it.
step 3: stop
}

Len()( operation
Input: none
Output: an integer value which represents the length of the queue
Algorithm len()
{
step 1: start
step 2: return the length of the queue
step 3: stop
}}
Program:
class ArrayQueue:
DEFAULT_CAPACITY = 10
def init (self):
self._data = [None]*ArrayQueue.DEFAULT_CAPACITY
self._size = 0
self._front = 0
def len (self):
return self._size
def is_empty(self):
return self._size == 0
def first(self):
if (self.is_empty() == True):
print(“queue is empty”)
else:
return self._data[self._front]
def dequeue(self):
if (self.is_empty() == True):
print(“Queue is empty”)
else:
answer = self._data[self._front]
self._data[self._front] = None
self._front = (self._front+1)% len(self._data)
self._size = self._size - 1
return answer
def enqueue(self, e):
if self._size == len(self._data):
self._resize(2*len(self._data))
else:
avail = (self._front + self._size) % len(self._data)
self._data[avail] = e
self._size += 1
def resize(self, cap): old =
self._data self._data =
[None]* cap
walk = self._front
for k in range(self._size):
self._data[k] = old[walk]
walk = (1+walk) % len(old)
self._front = 0
Ouput:
# Create a queue object and print the length of the queue and first element
obj1 = ArrayQueue()
print(“first element is: “, obj1.first())
print(“length is: “, len(obj1))
# enqueue 100, 200, 300 and print the length of the queue and first element
print(“\n”, “After enqueue”)
obj1.enqueue(100)
obj1.enqueue(200)
obj1.enqueue(300)
print(“first element is: “, obj1.first())
print(“length is: “, len(obj1))
# Dequeue first element and print the length of the queue and first element
print(“\n”, “After dequeue”)
obj1.dequeue()
print(“first element is: “, obj1.first())
print(“length is: “, len(obj1))

Result:
Thus, the above program runs successfully and the Output was verified.
Ex.no:6
Date: Application of Stack and Queue ADTs

Aim:
To write a python program to the application of Stack data structure and to
demonstrate its various operations
Algorithm:
1. Algorithm for infix to postfix conversion using stack data structure

Algorithm infix_to_postfix(input_string)
Input: input_string - string containing infix expression
Output: output_string - string containing postfix expression

Step 1: For each character c in the infix expression


if c is '('
push it into stack

elif c is ')'
while top_of_stack not equals '('
pop top_of_stack and add it to posfix_expression
ignore '(' without doing anything with it.

elif c is an alphabet
add it to posfix_expression

elif c is an operator
if top_of_stack is '('
push c into the stack
elif precedence(top_of_stack) is less than c
push c into the stack
elif precedence(top_of_stack) is not less than c
while(top_of_stack != '('
and stack is not empty
and precedence(top_of_stack) is not less than c)
pop top_of_stack and add it to postfix_expression
else
do-nothing

else
do-nothing

Step 2: while stack is not empty


pop top_of_stack and add it to postfix_expression

Step 3: return postfix_expression


Output:
2. Algorithm for infix to prefix conversion using stack data structure

Algorithm infix_to_prefix(input_string)
Input: input_string - string containing infix expression
Output: output_string - string containing prefix expression

step 1: Reverse the input_string and store the output in reverse_string.


During reversing process, replace '(' by ')' and vice-versa.
step 2: for the reverse_string, find the prefix_to_postfix conversion using stack
store the output in output_string
step 3: Reverse the output_string. We get prefix expression.
step 4: return it

Program:
class CustomLifo:
def infix_to_postfix(self, input_string):
# stack = LifoQueue(len(input_string))
stack = []
precedence = {'+':1, '-1':1, '*':2, '/':2, '^':3}
postfix_exp = ""

for char in input_string:


if char == '(':
stack.append(char)

elif char == ')':


top_char = stack.pop()
while (top_char != '('):
postfix_exp += top_char
top_char = stack.pop()

elif (ord(char)>=ord('a') and ord(char)<=ord('z')) or (char.isdigit()==True):


postfix_exp += char

else:
top_char = stack[-1]
print('top_char is: ', top_char, 'char is: ', char)
if top_char == '(':
stack.append(char)
elif (precedence[top_char]<precedence[char]) == True:
print('top_char is: ', top_char)
stack.append(char)
else:
while(len(stack) != 0
and top_char != '('
and (precedence[top_char]>=precedence[char] == True) ):
postfix_exp += stack.pop()
top_char = stack[-1]
print('char is: ', char, 'stack is: ', stack, 'output: ', postfix_exp)
while(len(stack) != 0):
postfix_exp += stack.pop()

return postfix_exp
def infix_to_prefix(self,input_str):
temp = ''
for x in input_str:
if x == '(':
temp = ')' + temp
elif x == ')':
temp = '(' + temp
else:
temp = x + temp
print('reversed input is: ', temp)
reverse_string = temp
output_string = self.infix_to_postfix(reverse_string)
output_string = output_string[-len(output_string):0:-1]
return output_string

s1 = CustomLifo()
# print(s1.infix_to_postfix('(a+b)')
# print(s1.infix_to_postfix('(a*b)')
# print(s1.infix_to_postfix('((a+b)*(c+d))'))
# print(s1.infix_to_postfix('(((5+2)*(2-1))/((2+9)+((7-1)-1))*8)'))
# print(s1.infix_to_prefix('((a+b)*(c-d))'))
# print( s1.infix_to_postfix('(a+b)') )
# print( s1.infix_to_postfix('(a+b*c)') )
# print( s1.infix_to_postfix('((a+b)*c)') )
print( s1.infix_to_postfix('((((3+1)*3)/((9-5)+2))-((3*(7-4))+6))') )

#Program for Application Of Queue ADTs


def check_palindrome(input_str):
queue1 = list(input_str)
for i in range(len(input_str)//2):
if queue1[i] != queue1[-1-i]:
print('not palindrome')
return
print('palindrome')
return
def check_palindrome(input_str):
queue1 = list(input_str)
for i in range((len(input_str)//2)):
if queue1[0] == queue1[-1-i]:
queue1.pop(0)
else:
print('not palindrome')
return
print('palindrome')
return
check_palindrome('malayalam')
check_palindrome('a')
check_palindrome('ab')
check_palindrome('aba')
check_palindrome('abab')
check_palindrome('abba')

Result:
Thus, the above program runs successfully and the Output was verified.
Ex.no.7
Date: Implementation of Sorting and Searching Algorithms

#BubbleSort
Aim:
To write python program using bubble sort to sort the given list of numbers
in the ascending order.
Algorithm:
input: unsorted array
output: sorted array
Algorithm bubble_sort()
a = [50, 40, 30, 20, 10]
for i from 1 to n-1
for j from i to n
if (a[j+1]<a[j])
swap(a[j+1], a[j])
end if
end for
end for
Output:
Program:

def Bubblesort(): # function definition


a = [50, 40, 30, 20, 10]
n = len(a)
for i in range (0, n):
for j in range (0, n-i-1):
if(a[j]&gt;a[j+1]):
temp = a[j]
a[j] = a[j+1]
a[j+1] = temp
print(“After iteration : “, i, “: “, a)
print(a)
Bubblesort() # function call

Result:
Thus the python program to sort the numbers using bubble sort has been
successfully executed and the results are verified.
#Insertion Sort
Aim:
To write python program using insertion sort to sort the given list of
numbers in the ascending order.

Algorithm:
input: unsorted array
output: sorted array
Algorithm insertion_sort()
a = [50, 40, 30, 20, 10]
for i from 2 to n
for j from i to 2
if (a[j]<a[j-1])
swap(a[j], a[j-1])
end if
end for
end for
Output:
Program:

def Insertionsort(): # function definition


a = [5, 40, 30, 45, 3]
print(“The given list is: “, a)
n = len(a)
for i in range(1, n, 1):
for j in range(i, 0, -1):
if(a[j]<a[j-1]):
(a[j], a[j-1]) = (a[j-1],a[j])
print(“iteration “, i, “: “, a)
print(“The sorted list is: “, a)
Insertionsort() # function call

Result:
Thus the python program to sort the numbers using insertion sort has been
successfully executed and the results are verified .
#MergeSort
Aim:
To write python program using Merge sort to sort the given list of
numbers
in the ascending order.
Algorithm:
Algorithm merge(s1, s2, s):
input: s1 - sorted lefthalf
s2 - sorted righthalf
s - outputlist of s1, s2
output: merged and sorted list of s1, s2
i=j=k=0
while(i<len(s1) and j<len(s2))
if(s1[i]<s2[j])
s[k] = s1[i]
i=i+1
k=k+1
else
s[k] = s2[j]
j=j+1
k=k+1
end if
while(i<len(s1))
s[k] = s1[i]
i=i+1
k=k+1
end while
while(j<len(s2))
s[k] = s2[j]
j=j+1
k=k+1
end while
Algorithm merge_sort(s):
input: s - unsorted array
output: s - sorted array
set n = len(s)
if (n<2)
return
else
set mid = n // 2;
s1 = s[0:mid]
merge_sort(s1)
s2 = s[mid:n]
merge_sort(s2)
merge(s1, s2, s)
end if
arr = [5, 40, 30, 45, 3]
print(“The given unsorted list is: “, arr)
merge_sort(arr) # function call
print(arr)

Program:
def merge(s1, s2, s):
# print(“s is: “, s)
i=j=k=0
while(i<len(s1) and j<len(s2)):
if(s1[i]<s2[j]):
s[k] = s1[i]
i=i+1
k=k+1
else:
s[k] = s2[j]
j=j+1
k=k+1
while(i<len(s1)):
s[k] = s1[i]
i=i+1
k=k+1
while(j<len(s2)):
s[k] = s2[j]
j=j+1
k=k+1
def merge_sort(s):
print(“mergesort called: s is: “, s)
n = len(s)
if (n<2):
return
else:
Output:
mid = n // 2; s1
= s[0:mid]
merge_sort(s1)
s2 = s[mid:n]
merge_sort(s2)

merge(s1, s2, s)
arr = [5, 40, 30, 45, 3]
print(“The given unsorted list is: “, arr)
merge_sort(arr)
print(arr)

Result:
Thus the python program to sort the numbers using Merge sort has been
successfully executed and the results are verified.
#QuickSort
Aim:
To write python program using Quick sort to sort the given list of
numbers
in the ascending order.
Algorithm:
procedure quicksort(A, left, right)
pivot = right
j = right - 1
i = left
while(i <= j)
while (i<=j and a[i] < a[pivot])
i++
while (i<=j and a[j] >=a[pivot])
j--
if(i<=j) # i, j have not crossed
swap(a[i], a[j])
i++, j++
else # i,j have crossed one another
swap(a[i], a[pivot])
quicksort(A, left, i-1)
quicksort(A, i+1, right)
end procedure
print the sorted list A
Output:
Program:
def Quicksort(a, left, right): # function definition
pivot = right
j = right-1
i = left
print(“pivot, j, i : “, pivot, j, i)
while(i <= j):
while (i<=j and a[i] < a[pivot]):
i = i+1
while (i<=j and a[j] >=a[pivot]):
j = j-1
if(i <= j): # i, j have not crossed
temp = a[i]
a[i] = a[j]
a[j] = temp
i = i+1
j = j+1
else: # i,j have crossed one another
temp = a[i]
a[i] = a[pivot]
a[pivot] = temp
Quicksort(a, left, i-1)
Quicksort(a, i+1, right)
return;
a = [50, 40, 30, 20, 10]
# a = [10, 20, 30, 40, 50]
#a = [10, 40, 30, 50, 20]
Quicksort(a, 0, len(a)-1) # function call
print (“The sorted list is : “, a)

Result:
Thus the python program to sort the numbers using Quick sort has been
successfully executed and the results are verified.
#SelectionSort
Aim:
To write python program using Selection sort to sort the given list of
numbers in the ascending order.

Algorithm:
input: unsorted array
output: sorted array

Algorithm Selection_sort()
a = [50, 40, 30, 20, 10]
for i from 1 to n-1
si = i
for j from i+1 to n
if (a[j]<a[si])
si = j
end if
end for
if (si != i)
swap(a[si], a[i])
end for
Output:
Program:
def Selectionsort(): # function definition
a = [5, 40, 30, 45, 3]
print(The given list is:, a)
n = len(a)
for i in range(0, n-1,1):
si = i
for j in range(i+1, n, 1):
if(a[j]<a[si]):
si = j
if (si != i):
(a[si], a[i]) = (a[i],a[si])
print(iteration, i, a)
print(The sorted list is: , a)
Selectionsort() # function call

Result:
Thus the python program to sort the numbers using Selection sort has been
successfully executed and the results are verified.
#LinearSearch
Aim:
To write a python program using tuples (or a list) to find a given item in a list of
items (linear search).
Algorithm:
step 1: start
step 2: Initialize the list as fruits = [banana, jackfruit, apple, orange,mango]
step 3: Get the item to searched in search_item variable
step 3: Initialize i to 0
step 4: if i less than length of the list-1
then step 5
else step 6
step 5: if search_item equals list[i]
then found=True and break the search loop
else increment i and goto step 4
step 6: if found == false
then print “element is not found”
then print “element is found”
step 7: stop
Output:
Program:

fruits = („banana”,””Jackfruit”,”apple”,”orange”, “mango”)


search_item = input(„Enter the search fruit: „)
found = False
for x in fruits:
if search_item == x:
found = True
break
if found == False:
print(„element not found‟)
else:
print(„element found‟)

Result:
The given program has been successfully executed and the results are verified.
Ex.no.8a
Date: Implementation of Hash Tables(a)

Aim:
To write a python program to the implementation of Hash Tables(a)- Separate
chaining.
Algorithm:
1. MapBas Class
1.1 Item Class
Step 1: Define an inner class `_Item` inside `MapBas` with slots `_key` and
value
Step 2: Implement `__init ` to initialize `_key` and `_value`.
Step 3: Implement `__eq ` and ` ne__` for equality checks.
Step 4: Implement `__lt ` for comparison based on `_key`.

1.2 MapBas Class


Step 1: Inherit from `MutableMapping`.
Step 2: Implement `__init `:
Step 3: Import `MutableMapping`.
Step 4: Define the inner class `_Item`.
Step 5:Provide slots and methods for comparison.
2. UnsortedTableMap Class
Step 1: Inherit from `MapBas`.
Step 2:Implement `__init `:
Step 3: Initialize an empty list `_table` as the underlying data structure.
Step 4: Implement `__getitem `:
Step5: Iterate through `_table` and return the value associated with the key.
Step6: Implement `__setitem `:
Step 7: Iterate through `_table` and update the value if the key exists, otherwise
append a new `_Item` to `_table`.
Step 8: Implement `__delitem `:
Step 9: Iterate through `_table` and delete the item with the specified key.
Step 10: Implement `__len `:
Step 11: Return the length of `_table`.
Step 12: Implement ` iter `:
3. HashMapBas Class
Step 1: Inherit from `MapBas`.
Step 2: Implement `__init `:
Step 3:Initialize `_table` as a list of size `cap` with `None` values.
Step 4: Initialize other attributes like `_n`, `_prime`, `_scale`, and `_shift`.
Step 5: Implement `_hash_function`:
Step 6: Use a hash function for computing the index in `_table`.
Step 7: Implement `__len `, `__getitem__`, `__setitem `, and `__delitem `:
Step 8: Use MAD compression and a hash function to manipulate the hashtable.
4. ChainHashMap Class
Step 1: Inherit from `HashMapBas`.
Step 2:Implement `_bucket_getitem`, `_bucket_setitem`, and
`_bucket_delitem`:
Step 3: Perform operations within the specified bucket of `_table`.
Step 4: Implement `__iter `:
Step 5: Yield keys by iterating through `_table`.
5. Example Usage
Step 1: Create an instance `T1` of `ChainHashMap`.
Step 2: Demonstrate adding items to the hash table using `_bucket_setitem` and
retrieving them using `_bucket_getitem`.
Program:

from collections.abc import MutableMapping

class MapBas(MutableMapping):
class _Item:
slots = '_key', '_value'

def init (self, k, v):


self._key = k
self._value = v

def eq (self, other):


return self._key == other._key

def ne__(self, other):


return not(self == other)

def It (self, other):


return self._key > other._key

"""
MutableMapping class not found
- https://stackoverflow.com/questions/70870041/cannot-import-name-
mutablemapping-from-collections

slots
-
https://wiki.python.org/moin/UsingSlots#:~:text=What%20Is%20%60 slots %60%
20%3F&text=The%20 slots %20declaration,declared%20in%20 slots .
"""

class UnsortedTableMap(MapBas):
def init (self):
self._table = [] # create an empty map

def getitem (self, k):


for item in self._table:
if k == item._key:
return item._value
raise KeyError('Key Error:' + repr(k))

def setitem (self, k, v):


# Assign value v to key k. If the existing value is present, then overwrite it.
for item in self._table:
if k == item._key:
item._value = v
return
self._table.append(self._Item(k,v))

def delitem (self, k):


# Remove item with key. Raise KeyError if key not found
for j in range(len(self._table)):
if k == self._table[j]._key:
self._table.pop(j)
return
raise KeyError ('Key Error:' + repr(k))

# Finding the length of the table. i.e. the number of items in the map or the hash
table.
def len (self):
""" Return the no. of items in the map. """
return len(self._table);

def iter (self):


"Generate iteration of the map's keys."""
for item in self._table:
yield item._key
"""
repr() - https://www.digitalocean.com/community/tutorials/python-str-repr-
functions
KeyError - To understand it, please refer https://realpython.com/python-keyerror/

Understanding getitem () method -


https://www.geeksforgeeks.org/ getitem -in-python/
class Test1:
def getitem (self, vale):
print("getitem method is called");
print("value is : ", value)
T1 = Test1()
T1[5] # automatically the getitem () method

"""

from random import randrange


""" https://www.w3schools.com/python/ref_random_randrange.asp """

class HashMapBas(MapBas):
def init (self, cap=11, p=109345121):
""" create an empt hash-table map. """
self._table = cap*[None]
self._n = 0 # no. of entries in the map
self._prime = p # prime no. for the MAD compression
self._scale = 1 + randrange(p-1) # scale from 1 to p-1 for MAD
self._shift = randrange(p) # scale from 0 to p-1 for MAD

def _hash_function(self, k):


temp = (hash(k) * self._scale + self._shift)%self._prime % len(self._table)
return temp

def len (self):


return self._n
def getitem (self, k):
j = self._hash_function(k)
return self._bucket_getitem(j,k) # may raise KeyError

def setitem (self, k):


j = self._hash_function(k)
self._bucket_setitem(j,k,v) # subroutine maintains self._n

def delitem (self, k):


j = self._hash_function(k)
self._bucket_delitem(j,k)
self._n = self._n - 1

class ChainHashMap(HashMapBas):
""" Hash map implemented with separate chaining for collision resolution """
def _bucket_getitem(self, j, k):
bucket = self._table[j]
if bucket is None:
raise KeyError ('Key Error: ' + repr(k)) # no match found
return bucket[k]

def _bucket_setitem(self, j, k, v):


if self._table[j] is None:
self._table[j] = UnsortedTableMap() # A call to UnsortedTableMap creates a
new table.
# Hence, a new table (or array) is assigned to self._table[j]
# i.e. here, we are dealing with two-dimensional tables.
# each table - is a separate chain - it is assumed so in the book
oldsize = len(self._table[j])
self._table[j][k] = v
if len(self._table[j]) > oldsize:
self._n = self._n +1

def _bucket_delitem(self, j, k):


bucket = self._table[j];
if bucket is None:
raise KeyError('key Error: ' + repr(k)) # no match found
del bucket[k] # may raise error
Output:
def iter__(self):
for bucket in self._table:
if bucket is not None:
for key in bucket:
yield key

""" hash table creation using the ChainHashMap object starts here """
T1 = ChainHashMap()

T1._bucket_setitem(0,0,10)
val = T1._bucket_getitem(0,0)
print("val is : ", val)

T1._bucket_setitem(0,1,20)
# this means
# 0 - bucket no (Each bucket has an array created in the HashMapBas class
# self._table = cap*[None]
# this line creates the array of size 11 (as mentioned in the cap variable)for the index
0
# 1 - key for the chain (a separate array of size 11) contained in the bucket no.
# 10 - value for the key 1 which is in the array (This array itself is in the bucket no. 0
of the UnsortedTableMap class)
val = T1._bucket_getitem(0,1)
print("val is : ", val)

T1._bucket_setitem(0,2,30) # 0th index in the HT, In that 0th index, we have a chain
of size 11. The key used here is 2 to associate the value 10 into that array.
val = T1._bucket_getitem(0,2)
print("val is : ", val)

Result:
Thus the above program was successfully executed and the output was verified.
Ex.no.8b
Date: Implementation of Hash Tables(b)

Aim:
To write a python program to the implementation of Hash Tables(b)-Linear
probing.
Algorithm:
Certainly! Here's a step-by-step algorithm for the provided Python program:
1. MapBas Class
1.1 `_Item` Class
Step 1: Define an inner class `_Item` inside `MapBas` with slots `_key` and
`_value`.
Step 2: Implement `__init ` to initialize `_key` and `_value`.
Step 3: Implement `__eq ` and ` ne__` for equality checks.
Step 4: Implement `__lt ` for comparison based on `_key`.
1.2 `MapBas` Class
Step 1: Inherit from `MutableMapping`.
Step 2: Implement `__init `:
Step 3: Import `MutableMapping`.
Step 4: Define the inner class `_Item`.
Step 5: Provide slots and methods for comparison.
2. UnsortedTableMap Class
Step 1: Inherit from `MapBas`.
Step 2:Implement `__init `:
Step 3: Initialize an empty list `_table` as the underlying data structure.
Step 4: Implement `__getitem `:
Step 5: Iterate through `_table` and return the value associated with the key.
Step 6: Implement `__setitem `:
Step 7: Iterate through `_table` and update the value if the key exists, otherwise
Step 8: append a new `_Item` to `_table`.
Step 8:Implement `__delitem `:
Step 9:Iterate through `_table` and delete the item with the specified key.
Step 10: Implement ` len `:
Step 11: Return the length of `_table`.
Step 12:Implement ` iter `:
Step 13: Yield keys by iterating through `_table`.
3. HashMapBas Class
Step 1:Inherit from `MapBas`.
Step 2: Implement `__init `:
Step 3:Initialize `_table` as a list of size `cap` with `None` values.
Step 4: Initialize other attributes like `_n`, `_prime`, `_scale`, and `_shift`.
Step 5: Implement `_hash_function`:
Step 6: Use a hash function for computing the index in `_table`.
Step 7:Implement `__len `, `__getitem `, `__setitem `, and ` delitem `:
Step 8: Use MAD compression and a hash function to manipulate the hash
table.
4. ProbeHashMap Class
Step 1: Inherit from `HashMapBas`.
Step 2: Implement `_AVAIL` as a sentinel to mark locations of previous
deletions.
Step 3: Implement `_is_available` to check if a slot is available.
Step 4:Implement `_find_slot` to search for a key in a specific bucket.
Step 5:Implement `_bucket_getitem`, `_bucket_setitem`, and
`_bucket_delitem`:
Step 6:Perform operations within the specified bucket of `_table`.
Step 7: Implement `__iter `:
Step 8:Yield keys by iterating through `_table`.
5. Example Usage
Step 1: Create an instance `T1` of `ProbeHashMap`.
Step 2: Demonstrate adding items to the hash table using `_bucket_setitem` and
Step 3: retrieving them using `_bucket_getitem`.
Step 4:The provided algorithm outlines the structure and functionality of the
given Python program, explaining the purpose of each class and method.

Program:

""" Linear Probing - from the book by Goodrich, Tamassia and Goldwasser """
from collections.abc import MutableMapping

class MapBas(MutableMapping):
class _Item:
slots = '_key', '_value'

def init (self, k, v):


self._key = k
self._value = v

def eq (self, other):


return self._key == other._key

def ne (self, other):


return not(self == other)
def It (self, other):
return self._key > other._key

"""
MutableMapping class not found
- https://stackoverflow.com/questions/70870041/cannot-import-name-mutablemapping-from-collections

slots
-
https://wiki.python.org/moin/UsingSlots#:~:text=What%20Is%20%60 slots %60%20%3F&text=The%20__slot
s %20declaration,declared%20in%20__slots .
"""

class UnsortedTableMap(MapBas):
def init (self):
self._table = [] # create an empty map

def getitem (self, k):


for item in self._table:
if k == item._key:
return item._value
raise KeyError('Key Error:' + repr(k))

def setitem (self, k, v):


# Assign value v to key k. If the existing value is present, then overwrite it.
for item in self._table:
if k == item._key:
item._value = v
return
self._table.append(self._Item(k,v))

def delitem (self, k):


# Remove item with key. Raise KeyError if key not found
for j in range(len(self._table)):
if k == self._table[j]._key:
self._table.pop(j)
return
raise KeyError ('Key Error:' + repr(k))

# Finding the length of the table. i.e. the number of items in the map or the hash table.
def len (self):
""" Return the no. of items in the map. """
return len(self._table);

def iter (self):


"Generate iteration of the map's keys."""
for item in self._table:
yield item._key
"""
repr() - https://www.digitalocean.com/community/tutorials/python-str-repr-functions
KeyError - To understand it, please refer https://realpython.com/python-keyerror/

Understanding getitem () method - https://www.geeksforgeeks.org/ getitem -in-python/


class Test1:
def getitem (self, vale):
print("getitem method is called");
print("value is : ", value)
T1 = Test1()
T1[5] # automatically the getitem () method

"""

from random import randrange


""" https://www.w3schools.com/python/ref_random_randrange.asp """

class HashMapBas(MapBas):
def init (self, cap=11, p=109345121):
""" create an empt hash-table map. """
self._table = cap*[None]
self._n = 0 # no. of entries in the map
self._prime = p # prime no. for the MAD compression
self._scale = 1 + randrange(p-1) # scale from 1 to p-1 for MAD
self._shift = randrange(p) # scale from 0 to p-1 for MAD

def _hash_function(self, k):


temp = (hash(k) * self._scale + self._shift)%self._prime % len(self._table)
return temp

def len (self):


return self._n

def getitem (self, k):


j = self._hash_function(k)
return self._bucket_getitem(j,k) # may raise KeyError

def setitem (self, k):


j = self._hash_function(k)
self._bucket_setitem(j,k,v) # subroutine maintains self._n

def delitem (self, k):


j = self._hash_function(k)
self._bucket_delitem(j,k)
self._n = self._n - 1

class ProbeHashMap(HashMapBas):
_AVAIL = object() # sentinel marks the locations of previous deletions
def _is_available(self, j):
# Return True if index j is available in table. """
return self._table[j] is None or self._table[j] is ProbeHashMap._AVAIL

def _find_slot(self, j, k):


# search for key k in bucket at index j.
"""
return (success, index) tuuple, described as follows.
If match was found, success is True and index denotes its location.
Else success is False and index denotes first available slot.
"""
firstAvail = None
while True:
if self._is_available(j):
if firstAvail is None:
firstAvail = j # mark this as first avail
Output:
if self._table[j] is None:
return (False, firstAvail)
elif k == self._table[j]._key:
return (True, j)
j = (j+1)%len(self._table)

def _bucket_getitem(self, j, k):


found, s = self._find_slot(j, k)
if not found:
raise KeyError("Key Error: " + repr(k)) # no match found
return self._table[s]._value

def _bucket_setitem(self, j, k, v):


found, s = self._find_slot(j,k)
if not found:
self._table[s] = self._Item(k,v) # insert a new item with key=k, value=v
self._n += 1 # increase the no.of element n size by 1
else:
self._table[s]._value = v # overwrite the existing value with the new value v

def _bucket_delitem(self, j, k):


found, s = self._find_slot(j, k)
if not found:
raise KeyError("Key Error : " + repr(k))
self._table[s] = ProbeHashMap._AVAIL # Mark that location as AVAIL instead of moving elements to the
left (In linear probing, we do this, till we find an empty slot)

def iter (self):


for j in range(len(self._table)):
if not self._is_available(j):
yield self._table[j]._key

""" Implementation of classes done till this end """

""" Now, we insert, delete, search some elements into this hash table using the linear probing technique """
T1 = ProbeHashMap() # create an object for the hash table (which is going to use the linear probing
implementation for searching, adding, deleting element from that hash table)

T1._bucket_setitem(0,0,10) # insert key=0, value=10 into the bucket 0


value = T1._bucket_getitem(0,0) # retrieve value for key 0 from the bucket 0
print("value is : ", value)

T1._bucket_setitem(0,1,20) # insert key=1, value=20 into the bucket 0


value = T1._bucket_getitem(0,1) # retrieve value for key 0 from the bucket 0
print("value is : ", value)

for j in T1._table:
if ( j is not None):
print(j._key, j._value)
else:
print(str(type(j)))

""" Also, Uncomment this comment and check the answer.


T1._bucket_setitem(5,100,101) # insert key=0, value=10 into the bucket 0
value = T1._bucket_getitem(5,100) # retrieve value for key 0 from the bucket 0
print("value is : ", value)
"""

T1._bucket_setitem(5,200,201) # insert key=1, value=20 into the bucket 0


value = T1._bucket_getitem(5,200) # retrieve value for key 0 from the bucket 0
print("value is : ", value)

for j in T1._table:
if ( j is not None):
print(j._key, j._value)
else:
print(str(type(j)))

Result:

Thus the above program was successfully executed and the output was verified.
Ex.no.9a
Date: Tree representation and traversal algorithms

Aim:
To write a python program to implement Tree representation and
traversal algorithms
Algorithm:
Tree Class:
1. Position Class:
Step 1: Abstract class representing the location of a single element in a
tree.
Step 2: Defines abstract methods: `element()`, `__eq (self, other)`,
`__ne (self, other).
2. Abstract Methods in `Tree` Class
Step 1:`root(self)`
Step 2: `parent(self, p)`
Step 3:`num_children(self, p)`
Step 4: `children(self, p)`
Step 5:` len (self)`
3. Implemented Methods in `Tree` Class
- `is_root(self, p)`
- `is_leaf(self, p)`
- `is_empty(self)`
BinaryTree` Class (Subclass of `Tree`):
1. Abstract Methods in `BinaryTree` Class
- `left(self, p)`
- `right(self, p)`

2. Implemented Methods in `BinaryTree` Class:


- `sibling(self, p)`
- `children(self, p)`

`LinkedBinaryTree` Class (Subclass of `BinaryTree`):

1. Node Class (`_Node`):


- Represents a node in the binary tree.
- Has attributes `_element`, `_parent`, `_left`, `_right`.
2. Position Class (Subclass of `BinaryTree.Position`):
- Represents the location of a single element in the binary tree.

3. Implemented Methods in `LinkedBinaryTree` Class:


Step 1 - `__init (self)`
Step 2- `_make_position(self, node)`
Step 3 - `__len (self)`
Step 4- `root(self)`
Step 5 - `parent(self, p)`
Step 6- `left(self, p)`
Step 7- `right(self, p)`
Step 8- `num_children(self, p)`
Step 9- `_add_root(self, e)`
Step 10 - `_add_left(self, p, e)`
Step 11- `_add_right(self, p, e)`
Step 12- `_replace(self, p, e)`
Step 13- `_delete(self, p)`

4. Usage Example (`T` is an instance of `LinkedBinaryTree`):


- Create an instance of `LinkedBinaryTree` (e.g., `T = LinkedBinaryTree()`).
- Use methods like `_add_root`, `_add_left`, `_add_right`, `_replace`, and
`_delete` to modify the tree.
- Access tree properties such as `_root`, `_size`, and various positions.

Program:

""" class Tree - definition starts """


class Tree:
# Abstract Base class representing a tree structure

class Position:
""" An abstraction representing the lcoation of a single element """
def element(self):
raise NotImplementedError('Position - must be implemented by a subclass')
def eq (self, other):
""" Return True if other Position represents the same location """
raise NotImplementedError('Position - must be implemented by a subclass')
def ne__(self, other):
""" Return True if other does not represent the same location """
raise NotImplementedError('Position - must be implemented by a subclass')
def root(self):
raise NotImplementedError('Tree - must be implemented by subclass')
def parent(self, p):
raise NotImplementedError('Tree - must be implemented by subclass')
def num_children(self, p):
raise NotImplementedError('Tree - must be implemented by subclass')
def children(self, p):
raise NotImplementedError('Tree - must be implemented by subclass')
def len (self):
raise NotImplementedError('Tree - must be implemented by subclass')

""" Implemented methods of the Tree class """


def is_root(self, p):
if self.root() == p:
return True
else:
return False
def is_leaf(self, p):
if self.num_children(p) == 0:
return True
else:
return False
def is_empty(self):
if len(self) == 0:
return True
else:
return False

""" class BinaryTree - definition starts """


class BinaryTree(Tree):
""" Abstract base class representing a inary tree structure. """

""" abstract methods """


def left(self, p):
raise NotImplementedError('Tree - must be implemented by subclass')
def right(self, p):
raise NotImplementedError('Tree - must be implemented by subclass')
""" abstract methods """

""" Implemented methods """


def sibling(self, p):
parent = self.parent(p)
if parent == None: # p is the root node
return None
else:
if p == self.left(parent):
return self.right(parent) # can be None or right node (if it exists)
else:
return self.left(parent) # can be None or left node (if it exists)
def children(self, p):
“““Generate an iteration of Positions representing the p's children. ”””
if self.left(p) != None:
yield self.left(p)
if self.right(p) != None:
yield self.right(p)
""" Implemented methods """

""" class LinkedBinaryTree - definition starts """


class LinkedBinaryTree(BinaryTree):
""" Implemented class - not an abstract class """

""" Node class starts here """


class _Node:
slots = '_element', '_parent', '_left', '_right'

def init (self, element, parent=None, left=None, right=None):


self._element = element
self._parent = parent
self._left = left
self._right = right

""" Position class - starts here """


class Position(BinaryTree.Position):
""" An abstraction representing the location of a single element. """
def init (self, container, node):
self._container = container
self._node = node
def element(self):
return self._node._element

def eq (self, other):


if type(other) == type(self) and other._node == self._node:
return true
else:
return False

def _validate(self, p):


# Return the associated position node, if position is valid. """
if not isinstance(p, self.Position):
raise TypeError('p must be a proper Position Type')
if p._container is not self:
raise ValueError('p does not belong to this container')
if p._node._parent is p._node:
raise ValueError('p is no longer valid. It is a deprecated node')
return p._node

def _make_position(self, node):


if node != None:
return self.Position(self, node)
else:
return None

# ----- LinkedBinaryTree class's constructor -----


def init (self):
self._root = None
self._size = 0
print("Inside LinkedBinaryTree constructor")

# ----- public accessors -----


def len (self):
return self._size
def root(self):
return self._make_position(self._root)
def parent(self, p):
node = self._validate(p)
return self._make_position(node._parent)
def left(self, p):
node = self._validate(p)
return self._make_position(node._left)
def right(self, p):
node = self._validate(p)
return self._make_position(node._right)

def num_children(self, p):


node = self._validate(p)
count = 0
if node._left != None:
count = count + 1
if node._right != None:
count = count + 1
return count # return the count

def _add_root(self, e):


print('Inside _add_root function')
if self._root != None:
raise ValueError('Root already exists')
self._size = 1
self._root = self._Node(e)
return self._make_position(self._root)

def _add_left(self, p, e):


print('Inside _add_left function')
node = self._validate(p)

if node._left != None:
raise ValueError('Left Child already exists')
self._size = self._size + 1
node._left = self._Node(e, node)
return self._make_position(node._left)

def _add_right(self, p, e):


node = self._validate(p)

if node._right != None:
raise ValueError('The right child already exists')
self._size = self._size + 1
node._right = self._Node(e, node)
return self._make_position(node._right)

def _replace(self, p, e):


node = self._validate(p)

old = node._element
node._element = e
return old
def _delete(self, p): # the node can be deleted if it has 0 or 1 children. It it has two
children, we return an error.
node = self._validate(p)
if self._num_children(p) ==2: # if the node has two children, it cannot be
deleted.
raise ValueError('p has two children. It cannot be deleted')

child = None # initialize child varibale to None before using it.


if node._left != None:
child = node._left
else:
child = node._right

""" step 1: Updating the child node's parent pointer """


if child != None: child._parent = node._parent# setting child's parent to its
grandparent. This is because, the child parent is being deleted now.

""" step 1: Updating the parent node's child pointer """


if node == self._root: self._root = child # If child exists, its address is stored in
the self._root variable. If child is None, then None is stored in the self._root variable.
else:
# parent = node._parent
if node == node._parent._left:
node._parent._left = child # # If child exists, its address is stored in the
node._parent._left variable. If child is None, then None is stored in the
node._parent._left variable.
else:
node._parent._right = child # # If child exists, its address is stored in the
node._parent._right variable. If child is None, then None is stored in the
node._parent._right variable.

""" reducing the size of the LinkedBinaryTree variable (here, it is T) """


self._size = self._size - 1
node._parent = node # convention for the deprecated node
return node._element
""" class implemenations end here """

T = LinkedBinaryTree()
print("_root is : ", T._root, " _size is : ", T._size)

print('')

root_position = T._add_root(10)
print('Root node is added now')
print("_root is : ", T._root, " _size is : ", T._size)
print("T_root_element is : ", T._root._element, "\n T_root_parent is : ",
T._root._parent, "\n T_root_leftchild is : ", T._root._left, "\n T_root_rightchild is : ",
T._root._right)
print('root position is : ', root_position)

left_position = T._add_left(root_position, 20)


print('')

print('Root node"s left child is added now')


print("_root is : ", T._root, " _size is (increased by 1 now): ", T._size)
print("T_root_element is : ", T._root._element, "\n T_root_parent is : ",
T._root._parent, "\n T_root_leftchild is (is changed now) : ", T._root._left, "\n
T_root_rightchild is : ", T._root._right)
print('left node"s position is : ', left_position)

right_position = T._add_right(root_position, 30)


print('')

print('Root node"s right child is added now')


print("_root is : ", T._root, " _size is (increased by 1 now): ", T._size)
print("T_root_element is : ", T._root._element, "\n T_root_parent is : ",
T._root._parent, "\n T_root_leftchild is (is changed now) : ", T._root._left, "\n
T_root_rightchild is : ", T._root._right)
print('right node"s position is : ', right_position)

print('Length of the tree T is : ', len(T))


print('root node element is : ', T.root()._node._element)
print('Parent of left_position is : ', T.parent(left_position)._node._element)
print('Left node of root_position is : ', T.left(root_position)._node._element)
print('Right node of root_position is : ', T.right(root_position)._node._element)
print('No. of children of root_position is : ', T.num_children(root_position))
print('No. of children of left_position is : ', T.num_children(left_position))

T._replace(root_position, 100);
print('')
print('Replaced root_element 10 with 100')
print('root node element is : ', T.root()._node._element)
print('root node element is : ', T.root()._node._element)

Result:
Thus the above program was successfully verified and the output was verified.
Ex.no.9b
Date: Tree Representation and Traversal Algorithms(b)

Aim:
To write a python program to implement Tree Representation and Traversal
Algorithms(b).

Algorithm:
1. LinkedBinaryTree Class
1.1 `_Node` Class
Step 1: Define an inner class `_Node` inside `LinkedBinaryTree` with slots
`_element`, `_parent`, `_left`, and `_right`.
Step 2: Implement ` init ` to initialize `_element`, `_parent`, `_left`, and
`_right`.
1.2 `LinkedBinaryTree` Class
Step 1: Implement the constructor (` init `) to initialize `_root` and `_size`.
Step 2: Implement public accessors:
Step 3:`__len ` returns the size of the tree.
Step 4: `root` returns the root node.
Step 5: `parent`, `left`, and `right` return the respective nodes for a given node.
Step 6: `num_children` returns the number of children for a given node.
1.3 Additional Methods
Step 1: `_add_root` adds the root node to the tree.
Step 2: `_add_left` and `_add_right` add left and right children to a given node.
Step 3: `_replace` replaces the element of a given node.
Step 4:`_delete` deletes a node if it has 0 or 1 child.

2. Example Usage
Step 1: Create an instance `T` of `LinkedBinaryTree`.
Step 2: Demonstrate adding the root and left/right children using `_add_root`,
`_add_left`, and `_add_right`.
Step 3: Demonstrate accessing tree properties using public accessors.
Step 4: Demonstrate element replacement using `_replace`.
Step 5: Demonstrate node deletion using `_delete`.
Program:
""" class LinkedBinaryTree - definition starts """
class LinkedBinaryTree:

# ----- LinkedBinaryTree class's constructor -----


def init (self):
self._root = None
self._size = 0
print("Inside LinkedBinaryTree constructor")

""" Node class starts here """


class _Node:
slots = '_element', '_parent', '_left', '_right'

def init (self, element, parent=None, left=None, right=None):


self._element = element
self._parent = parent
self._left = left
self._right = right

# ----- public accessors -----


def len (self):
return self._size
def root(self):
return self._root
def parent(self, node):
return node._parent
def left(self, node):
return node._left
def right(self, node):
return node._right

def num_children(self, node):


count = 0
if node._left != None:
count = count + 1
if node._right != None:
count = count + 1
return count # return the count

def _add_root(self, e):


print('Inside _add_root function')
if self._root != None:
raise ValueError('Root already exists')
self._size = 1
self._root = self._Node(e)
return self._root

def _add_left(self, node, e):


print('Inside _add_left function')

if node._left != None:
raise ValueError('Left Child already exists')
self._size = self._size + 1
node._left = self._Node(e, node)
return node._left

def _add_right(self, node, e):


if node._right != None:
raise ValueError('The right child already exists')
self._size = self._size + 1
node._right = self._Node(e, node)
return node._right

def _replace(self, node, e):

old = node._element
node._element = e
return old

def _delete(self, node): # the node can be deleted if it has 0 or 1 children. It it has two children, we return an
error.

if self.num_children(node) ==2: # if the node has two children, it cannot be deleted.


print (node._element, ' has two children. It cannot be deleted')
return

# Note: If the above case is correct, the next statement will not be reached
child = None # initialize child varibale to None before using it.
if node._left != None:
child = node._left
else:
child = node._right

""" step 1: Updating the child node's parent pointer - only if child is not none """
if child != None: child._parent = node._parent# setting child's parent to its grandparent. This is because, the
child parent is being deleted now.

""" step 1: Updating the parent node's child pointer """


if node == self._root: self._root = child # If child exists, its address is stored in the self._root variable. If child
is None, then None is stored in the self._root variable.
else:
# parent = node._parent
if node == node._parent._left:
node._parent._left = child # # If child exists, its address is stored in the node._parent._left variable. If
child is None, then None is stored in the node._parent._left variable.
else:
node._parent._right = child # # If child exists, its address is stored in the node._parent._right variable. If
child is None, then None is stored in the node._parent._right variable.

""" reducing the size of the LinkedBinaryTree variable (here, it is T) """


self._size = self._size - 1
node._parent = node # convention for the deprecated node
return node._element
""" class implemenations end here """

T = LinkedBinaryTree()
print("_root is : ", T._root, " _size is : ", T._size)

print('')

root_position = T._add_root(10)
print('Root node is added now')
Output:
print("_root is : ", T._root, " _size is : ", T._size)
print("T_root_element is : ", T._root._element, "\n T_root_parent is : ", T._root._parent, "\n T_root_leftchild is : ",
T._root._left, "\n T_root_rightchild is : ", T._root._right)
print('root position is : ', root_position)

left_position = T._add_left(root_position, 20)


print('')

print('Root node"s left child is added now')


print("_root is : ", T._root, " _size is (increased by 1 now): ", T._size)
print("T_root_element is : ", T._root._element, "\n T_root_parent is : ", T._root._parent, "\n T_root_leftchild is (is
changed now) : ", T._root._left, "\n T_root_rightchild is : ", T._root._right)
print('left node"s position is : ', left_position)

right_position = T._add_right(root_position, 30)


print('')

print('Root node"s right child is added now')


print("_root is : ", T._root, " _size is (increased by 1 now): ", T._size)
print("T_root_element is : ", T._root._element, "\n T_root_parent is : ", T._root._parent, "\n T_root_leftchild is (is
changed now) : ", T._root._left, "\n T_root_rightchild is : ", T._root._right)
print('right node"s position is : ', right_position)

print('Length of the tree T is : ', len(T))


print('root node element is : ', T.root()._element)
print('Parent of left_position is : ', T.parent(left_position)._element)
print('Left node of root_position is : ', T.left(root_position)._element)
print('Right node of root_position is : ', T.right(root_position)._element)
print('No. of children of root_position is : ', T.num_children(root_position))
print('No. of children of left_position is : ', T.num_children(left_position))

T._replace(root_position, 100);
print('')
print('Replaced root_element 10 with 100')
print('root node element is : ', T.root()._element)
print('root node element is : ', T.root()._element)

""" Deletion """


# case 1 - Try to delete a node which has two children - it is not possible.
T._delete(root_position)

node4 = T._add_left(left_position, 40)


print('No. of children of left_position is : ', T.num_children(left_position))

node5 = T._add_right(right_position, 60)


print('No. of children of right_position is : ', T.num_children(right_position))
print('Length of the tree T is : ', len(T))

# case 2 - Try to delete a node with only one child - it is possible.


# In our case, we delete 20 (Its parent is 100, its child is 40)
T._delete(left_position)
print('')
print("Node with one child is deleted")
print('Length of the tree T is : ', len(T))
print('Left node of root_position is : ', T.left(root_position)._element)

T._delete(right_position)
print('')
print("Node with one child is deleted")
print('Length of the tree T is : ', len(T))
print('Right node of root_position is : ', T.right(root_position)._element)

# case 2 - Try to delete a node with zero child - it is possible.


T._delete(node4)
print('')
print("Node with no child is deleted")
print('Length of the tree T is : ', len(T))
print('Left node of root_position is : ', T.left(root_position))
print('Right node of root_position is : ', T.right(root_position)) # it exists
print('Length of the tree T is : ', len(T))

Result:
Thus the above program was successfully run and the output was verified.
Ex.no.9c
Date: Tree Representation and Traversal Algorithms(c)

Aim:
To write a python program to implement Tree Representation and Traversal
Algorithms(c).
Algorithm:
1. LinkedBinaryTree Class
1.1 `_Node` Class
Step 1: Define an inner class `_Node` inside `LinkedBinaryTree` with slots
`_element`, `_parent`, `_left`, and `_right`.
Step 2:- Implement ` init ` to initialize `_element`, `_parent`, `_left`, and
`_right`.
1.2 `LinkedBinaryTree` Class
Step 1: Implement the constructor (` init `) to initialize `_root` and `_size`.
Step 2:Implement public accessors:
Step 3:`__len ` returns the size of the tree.
Step 4:`root` returns the root node.
Step 5:`parent`, `left`, and `right` return the respective nodes for a given node.
Step 6: `num_children` returns the number of children for a given node.
Step 7: Implement methods to add the root node (`_add_root`), left child
(`_add_left`), right child (`_add_right`).
Step 8: Implement methods to replace the element of a node (`_replace`) and
delete a node with 0 or 1 children (`_delete`).
1.3 Tree Traversals
Step 1: Implement a preorder traversal method (`_preorder`) to print the
elements of the tree in the order: node, left subtree, right subtree.
2. Example Usage
Step 1: Create an instance `T` of `LinkedBinaryTree`.
Step 2: Demonstrate adding the root and left/right children using `_add_root`,
Step Step 3:`_add_left`, and `_add_right`.
Step 4: Demonstrate accessing tree properties using public accessors.
Step 5: Demonstrate element replacement using `_replace`.
Step 6: Demonstrate node deletion using `_delete`.
Step 7:Perform a preorder traversal using `_preorder` and print the elements.
Program:
""" class LinkedBinaryTree - definition starts """
class LinkedBinaryTree:

# ----- LinkedBinaryTree class's constructor -----


def init (self):
self._root = None
self._size = 0
print("Inside LinkedBinaryTree constructor")

""" Node class starts here """


class _Node:
slots = '_element', '_parent', '_left', '_right'

def init (self, element, parent=None, left=None, right=None):


self._element = element
self._parent = parent
self._left = left
self._right = right

# ----- public accessors -----


def len (self):
return self._size
def root(self):
return self._root
def parent(self, node):
return node._parent
def left(self, node):
return node._left
def right(self, node):
return node._right

def num_children(self, node):


count = 0
if node._left != None:
count = count + 1
if node._right != None:
count = count + 1
return count # return the count

def _add_root(self, e):


print('Inside _add_root function')
if self._root != None:
raise ValueError('Root already exists')
self._size = 1
self._root = self._Node(e)
return self._root

def _add_left(self, node, e):


print('Inside _add_left function')

if node._left != None:
raise ValueError('Left Child already exists')
self._size = self._size + 1
node._left = self._Node(e, node)
Output:
return node._left

def _add_right(self, node, e):

if node._right != None:
raise ValueError('The right child already exists')
self._size = self._size + 1
node._right = self._Node(e, node)
return node._right

def _replace(self, node, e):

old = node._element
node._element = e
return old

def _delete(self, node): # the node can be deleted if it has 0 or 1 children. It it has two children, we return an
error.

if self.num_children(node) ==2: # if the node has two children, it cannot be deleted.


print (node._element, ' has two children. It cannot be deleted')
return

# Note: If the above case is correct, the next statement will not be reached
child = None # initialize child varibale to None before using it.
if node._left != None:
child = node._left
else:
child = node._right

""" step 1: Updating the child node's parent pointer - only if child is not none """
if child != None:
child._parent = node._parent# setting child's parent to its grandparent. This is because, the child parent
is being deleted now.

""" step 1: Updating the parent node's child pointer """


if node == self._root:
self._root = child # If child exists, its address is stored in the self._root variable. If child is None, then
None is stored in the self._root variable.
else:
# parent = node._parent
if node == node._parent._left:
node._parent._left = child # # If child exists, its address is stored in the node._parent._left variable. If
child is None, then None is stored in the node._parent._left variable.
else:
node._parent._right = child # # If child exists, its address is stored in the node._parent._right variable. If
child is None, then None is stored in the node._parent._right variable.
""" reducing the size of the LinkedBinaryTree variable (here, it is T) """
self._size = self._size - 1
node._parent = node # convention for the deprecated node
return node._element

""" Tree Traversals """


#Preorder traversal
def _preorder(self, node):
print(node._element)
if(node._left != None):
self._preorder(node._left)
if(node._right != None):
self._preorder(node._right)

""" class implemenations end here """

T = LinkedBinaryTree()
print("_root is : ", T._root, " _size is : ", T._size)

print('')

print ('case 1 - only root node')


root_position = T._add_root(10)
T._preorder(root_position)

print ('case 2 - root node, its left child')


left_position = T._add_left(root_position, 20)
T._preorder(root_position)

print ('case 3 - root node, its left child, right child')


right_position = T._add_right(root_position, 30)
T._preorder(root_position)

print ('case 4 - root node, its left child, right child, left node"s left child')
node4 = T._add_left(left_position, 40)
T._preorder(root_position)

print ('case 4 - root node, its left child, right child, left node"s both left child and right child')
node5 = T._add_right(left_position, 50)
T._preorder(root_position)

Result:

Thus the above program was successfully run and the output was verified.
Ex.no.10
Date: Binary Search Tree

Aim:
The aim of the algorithms in the provided program is to implement
basic operations on a Binary Search Tree (BST).
ALGORITHM:
1) BSTInsertion Algorithm (BST_insert):
To insert a new element into the BST while maintaining the properties
of a BST.
Steps:
1. If the tree is empty, add the element as the root.
2. Otherwise, traverse the tree from the root until an appropriate position
is found (based on the comparison of the element with the current
node's value).
3. Insert the new element as the left child if it is less than the current
node's value, or as the right child if it is greater.
2) BSTDeletion Algorithm (BST_delete):
To delete a specified element from the BST while maintaining the
properties of a BST.
Steps:
1. Search for the node containing the element to be deleted.
2. If the node has two children, find the in-order predecessor (or
successor), replace the node's value with it, and then recursively delete
the predecessor (or successor).
3. If the node has one or zero children, update the parent's pointer to skip
the node and delete it.

PROGRAM:
""" class LinkedBinaryTree- definition starts """
class LinkedBinaryTree:
#----- LinkedBinaryTree class's constructor----
def init (self):
self._root = None
self._size = 0
print("Inside LinkedBinaryTree constructor")
""" Node class starts here """
class _Node:
slots = '_element', '_parent', '_left', '_right'
def init (self, element, parent=None, left=None, right=None):
self._element = element
self._parent = parent
self._left = left
self._right = right
#----- public accessors----
def len (self):
return self._size
def root(self):
return self._root
def parent(self, node):
return node._parent
def left(self, node):
return node._left
def right(self, node):
return node._right
def num_children(self, node):
count = 0
if node._left != None:
count = count + 1
if node._right != None:
count = count + 1
return count # return the count
def _add_root(self, e):
print('Inside _add_root function')
if self._root != None:
raise ValueError('Root already exists')
self._size = 1
self._root = self._Node(e)
return self._root
def _add_left(self, node, e):
print('Inside _add_left function')
if node._left != None:
raise ValueError('Left Child already exists')
self._size = self._size + 1
node._left = self._Node(e, node)
return node._left
def _add_right(self, node, e):
if node._right != None:
raise ValueError('The right child already exists')
self._size = self._size + 1
node._right = self._Node(e, node)
return node._right
def _replace(self, node, e):
old = node._element
node._element = e
return old
# the node can be deleted if it has 0 or 1 children. It it has two children, we
return an error.
def _delete(self, node):
if self.num_children(node) ==2:
# if the node has two children, it cannot be deleted.
print (node._element, ' has two children. It cannot be deleted')
return
# Note: If the above case is correct, the next statement will not be reached
child = None # initialize child varibale to None before using it.
if node._left != None:
child = node._left
else:
child = node._right
""" step 1: Updating the child node's parent pointer- only if child is not none
"""
if child != None:
child._parent = node._parent # setting child's parent to its
grandparent.
This is because, the child parent is being deleted now.
""" step 1: Updating the parent node's child pointer """
if node == self._root:
self._root = child # If child exists, its address is stored in
the self._root variable. If child is None, then None is stored in the self._root
variable.
else:
# parent = node._parent
if node == node._parent._left:
node._parent._left = child # # If child exists, its address is
stored in the
node._parent._left variable. If child is None, then None is
stored in the
node._parent._left variable.
else:
node._parent._right = child # # If child exists, its address is
stored in the
node._parent._right variable. If child is None, then None is
stored in the
node._parent._right variable.
""" reducing the size of the LinkedBinaryTree variable (here, it is T) """
self._size = self._size- 1
node._parent = node # convention for the deprecated node
return node._element
""" Tree Traversals """
#inorder traversal
def _inorder(self, node):
if(node._left != None):
self._inorder(node._left)
print(node._element)
if(node._right != None):
self._inorder(node._right)
""" Binary Search Tree- code starts here """
def BST_insert(self, k):
#if self.is_empty():
if self._root == None:
self._add_root(k)
print("Add root called now")
else:
p =self._subtree_search(self.root(),k)
print("p is : ", p, "p._element is : ", p._element)
if p._element == k:
print("Element : ", k, " is already in the list. Pls. insert
another element")
return;
else:
if p._element < k:
self._add_right(p, k)
else:
self._add_left(p,k)
def _subtree_search(self, p, k):
""" Return the node of the subtree- into which the new key value has to
inserted. """
""" Insertion as left child or right child is not done in this method. Only the
node is found"""
if p != None and k == p._element:
return p
elif k < p._element:
if self.left(p) != None:
return self._subtree_search(self.left(p), k)
else: return p # if k < node value and node has no left child, then
return that
node.
else:
if self.right(p) != None:
return self._subtree_search(self.right(p), k)
else: return p # if k > node value and node has no right
child, then return
that node.

def BST_delete(self, k):


# step 1: Find the node with the key k
p =self._subtree_search(self.root(),k)
if (p == None): # if k is not in the tree at all.
print ("The node with value : ", k, " is not in the tree T")
return;
else:
# node with value k is present in the tree.
# step 2: Remove the node with the key value k
if self.left(p) != None and self.right(p) != None:
replacement = self._subtree_last_position(self.left(p))
# i.e find the node with biggest value in the leftsub tree of the node p
# When found, this node will be used as the replacement for the node
with alue k
self._replace(p, replacement._element) # replacement done here.
p =replacement
parent = self.parent(p)
self._delete(p)

def _subtree_last_position(self, p):


""" Find the rightmost leaf in the tree starting at p """
temp = p
while self.right(temp) != None:
temp = self.right(temp)
return temp
""" Binary Search Tree code- ends here """
""" class implemenations end here """
T =LinkedBinaryTree()
print("_root is : ", T._root, " _size is : ", T._size)
print( ')
print ('case 1- only root node')
root_position = T._add_root(10)
T.BST_insert(20)
# T.BST_insert(10)
T.BST_insert(5)
# T.BST_insert(0)
print ("In-order travesal of the tree node is: ")
T._inorder(root_position)
print("p._left._element is ", T.root()._left._element)
print("p._left._left is ", T.root()._left._left)
print("p._left is ", T.root()._left)
print("p._right._element is ", T.root()._right._element)
print("p._right._right is ", T.root()._right._right)
print("p._right is ", T.root()._right)
T.BST_insert(30)
T.BST_insert(1)
T.BST_insert(0)
print ("In-order travesal of the tree node is: ")
T._inorder(root_position)
T.BST_insert(0)
T._inorder(root_position)
print ("After deleting 0")
T.BST_delete(0)
T._inorder(root_position)
print ("After deleting 0")
T.BST_delete(20)
T._inorder(root_position)
153
print ("After inserting 20")
T.BST_insert(20)
T._inorder(root_position)
print ("After deleting 10")
T.BST_delete(10)
T._inorder(root_position)
print ("After inserting 10")
T.BST_insert(10)
T._inorder(root_position)

Result:

Thus the above program was successfully run and the output was verified.
Ex.no.11
Date: Heap (Priority Queue)

AIM:
To write a python program to implement min-queue with support for operations
such as upheap() during insertion of new node, downheap() during deletion of the min
node at the root position.

ALGORITHM:
procedure remove_min():

Inputs: no parameters received.


By default, we remove the root node and
replace it with the last node in the last level.
Then, we go for downheap.
Output: root node's key, value
if array is empty:
print ('Priority queue is empty.')
return
else
swap root node and last node
remove the last node from the list
call downheap(root node)
return the removed last node's key, value
procedure downheap():

Inputs: j - index of the node


Output: NULL small_child= min(j‟s left_child, j‟s right_child)
if small_child's key < node at index j's key
swap key(node j, small_child)
recursively call downheap(small_child)
end if
else
do nothing
return
end if
procedure add(key, value):

input: key - key to be inserted


value - corresponding value for the key
output: nothing
create a new Item(key,value) and append it at the end of the list
call upheap(len(self._data)-1)
procedure upheap(j):

input: j representing the index of the node for upheap process


output: nothing retured. But, heap property restored after the insertion of new
key,value.
parent = node j's parent node
if j>0 :
if node j's key < node j's parent's key
swap(node j, parent node)
call uphead(parent node)
end if
else # we have already processed the root node
do nothing. # we are going above the root node.
return
end if

PROGRAM:

class HeapPriorityQueue:
class _Item:
slots = '_key', '_value'
def init (self, k, v):
self._key = k
self._value = v
# To compare, one list item with another list item.
# Here, we use user-defined Item for data in the arrays.
# So, we need to give this function for comparing two items.
def lt (self, other):
return self._key < other._key
def is_empty(self):
return len(self) == 0

def init (self):


""" Create a new empty Priority Queue as a python list """
self._data = []
def _parent(self, j):
print ('Inside parent :', (j-1)//2)
return (j-1) // 2
def _left(self, j):
return (2*j) + 1
def _right(self, j):
return (2*j) + 2
def _has_left(self, j):
if self._left(j) < len(self._data):
return True
else:
return False

def _has_right(self, j):


if self._right(j) < len(self._data):
return True
else:
return False
def _swap(self, i, j):
temp = self._data[i]
self._data[i] = self._data[j]
self._data[j] = temp

def _upheap(self, j):


parent = self._parent(j);
print('j is: ', j);
if j>0 and self._data[j] < self._data[parent]:
self._swap(j, parent)
self._upheap(parent)
def _downheap(self, j):
if self._has_left(j) == True:
left = self._left(j)
small_child = left
if self._has_right(j) == True:
right = self._right(j)
if self._data[right] < self._data[left]:
small_child = right
if self._data[small_child] < self._data[j]:
self._swap(j, small_child)
self._downheap(small_child)
# ------ public behaviours ----------- #
def len (self):
return len(self._data)
def add(self, key, value):
self._data.append(self._Item(key,value))
self._upheap(len(self._data)-1)
def min(self):
""" Return but do not remove (k,v) tuple with the minimum key. """
if self.is_empty():
raise Empty('Priority queue is empty.')
item = self._data[0]
return (item._key, item._value)
def remove_min(self):
""" Remove and return (k,v) tuple with minimum key.
Raise error if queue is empty """
if self.is_empty():
raise Empty('Priority queue is empty.')
self._swap(0, len(self._data)-1) # put minimum item at the
end
item = self._data.pop() # and remove it from the list;
self._downheap(0)
return (item._key, item._value)

def display(self):
if len(self._data) == 0:
print('queue is empty.')
return
for tup in self._data:
print ('key is ', tup._key, ' value is :', tup._value)

""" Heap implementation - ends here """

q1 = HeapPriorityQueue() # q1 represents the newly created priority queue now.


q1.display() # display the empty priority queue
print("Adding 4, C")
q1.add(4,'C')
q1.display()

print("\n", "Adding 12 more key-value pairs into this tree. Then, ")

q1.add(5,'A')
q1.add(6,'Z')
q1.add(15,'K')
q1.add(9,'F')
q1.add(7,'Q')
q1.add(20,'B')
q1.add(16,'X')
q1.add(25,'J')
q1.add(14,'E')
q1.add(12,'H')
q1.add(11,'S')
q1.add(13,'W')
q1.display()

# Now, I want to see the min element.

k,v = q1.min()
print('The minimum key is : ', k, ' value is : ', v)
9
'''# Now, insert (2,T).
q1.add(2,'T')
print("\n", "After adding the element, the list is: ")
q1.display()

''' # Then, I want to remove the minimum element.

q1.remove_min()
print("\n", "After removing the element, the list is: ")
q1.display()

RESULT:
Thus the python program to implement min-queue (priority queue) has been
successfully executed and the results are verified.
Ex.no.12(a) Graph Representation and Traversal algorithm
Date: (undirected graph)

Aim:
The aim of the provided code is to implement a simple undirected graph using
classes in Python.

Algorithm:
1. Vertex Class
1. Vertex Class Creation
o Step 1.1: Define a Vertex class to represent graph vertices.
o Step 1.2: Define an init method that initializes the vertex with an element x.
o Step 1.3: Define the element() method to return the element of the vertex.
o Step 1.4: Define the hash () method to ensure the vertex can be used as a key in
dictionaries or sets.
2. Edge Class
2. Edge Class Creation
o Step 2.1: Define an Edge class to represent directed edges in the graph.
o Step 2.2: Define an init method that initializes the edge with:
u: The origin vertex.
v: The destination vertex.
x: The element (label or weight) associated with the edge.
o Step 2.3: Define the endPoints() method to return the origin and destination vertices
of the edge.
o Step 2.4: Define the opposite(v) method to find the opposite vertex of v on the edge.
o Step 2.5: Define the element() method to return the element (label or weight) of the
edge.
o Step 2.6: Define the hash () method to ensure edges can be used as keys in
dictionaries or sets.
3. Graph Class
3. Graph Class Creation
o Step 3.1: Define a Graph class that represents a directed graph.
o Step 3.2: Initialize the Graph class with an empty dictionary _outgoing to store edges.
This dictionary will map each vertex to its outgoing edges.
o Step 3.3: Define the vertices() method that returns all vertices in the graph by
returning the keys of _outgoing.
o Step 3.4: Define the edges() method that returns all edges in the graph by collecting
all edge values from the _outgoing dictionary.
o Step 3.5: Define the insert_vertex(x) method:
Create a new Vertex with the value x.
Add the vertex to _outgoing as a key with an empty dictionary as its value.
Return the created vertex.
o Step 3.6: Define the insert_edge(u, v, x) method:
Create a new Edge object with the origin vertex u, destination vertex v, and
the element x.
Add the edge to both u's and v's outgoing edge dictionaries in _outgoing (this
makes the graph undirected by adding the edge in both directions).
Return the created edge.
o Step 3.7: Define the incident_edges(v, outgoing=True) method:
If outgoing is True, return all edges where vertex v is the origin.
Otherwise, return all edges where vertex v is the destination.
o Step 3.8: Define the adjacent_vertices(v, outgoing=True) method:
If outgoing is True, return all vertices adjacent to v via outgoing edges.
If outgoing is False, return all vertices adjacent to v via incoming edges.
o Step 3.9: Define the edges_count() method to count the total number of edges in the
graph by iterating through _outgoing and counting all the edges.
o Step 3.10: Define the vertex_count() method to return the total number of vertices in
the graph by counting the number of keys in _outgoing.
o Step 3.11: Define the degree(v, outgoing=True) method:
Return the number of outgoing edges from v if outgoing=True.
Return the number of incoming edges to v if outgoing=False.
4. Example: Graph Creation and Queries
4. Create a Graph Object
o Step 4.1: Create an instance of the Graph class (e.g., G1 = Graph(False)).
5. Add Vertices to the Graph
o Step 5.1: Insert vertices u, v, w, z into the graph by calling insert_vertex().
6. Add Edges to the Graph
o Step 6.1: Insert edges between vertices:
Add edge e between vertices u and v by calling insert_edge(u, v, 'e').
Add edge f between vertices v and w by calling insert_edge(v, w, 'f').
Add edge g between vertices u and w by calling insert_edge(u, w, 'g').
Add edge h between vertices w and z by calling insert_edge(w, z, 'h').
7. Print Vertices and Edges
o Step 7.1: Print all vertices in the graph using vertices().
o Step 7.2: Print all edges in the graph using edges().
8. Query Incident Edges
o Step 8.1: For each vertex (e.g., u, v, w, z), print the incident edges by calling
incident_edges(v).
9. Query Adjacent Vertices
o Step 9.1: For each vertex (e.g., u, v, w, z), print the adjacent vertices by calling
adjacent_vertices(v).
10. Count Vertices and Edges
o Step 10.1: Print the number of vertices in the graph using vertex_count().
o Step 10.2: Print the number of edges in the graph using edges_count().
11. Query Degree of Vertices
o Step 11.1: For each vertex (e.g., u, v, w, z), print its degree (out-degree or in-degree)
by calling degree(v).

Program:
# ------------------------- The Vertex class -------------------------
class Vertex:
slots = '_element'

def init (self, x):


self._element = x

def element(self):
return self._element

def hash (self):


return hash(id(self))

# ------------------------- The Edge class ---------------------------


class Edge:
slots = '_origin', '_destination', '_element'

def init (self, u, v, x):


self._origin = u
self._destination = v
self._element = x

def endPoints(self):
return (self._origin, self._destination)

def opposite(self, v):


return self._destination if self._origin == v else self._origin

def element(self):
return self._element

def hash (self):


return hash(id(self))

# ------------------------- The graph class ---------------------------


class Graph: # This graph can be used as directed graph only. Direction items are removed.

def init (self, directed=False):


self._outgoing = {}

def vertices(self):
return self._outgoing.keys()

def edges(self):
edges = set()
for secondary_map in self._outgoing.values():
edges.update(secondary_map.values())
# Update operation avoids duplicate values. Also, set will not contain duplicate values as
well.
return edges

def insert_vertex(self, x=None):


v =Vertex(x)
self._outgoing[v] = {}
return v

def insert_edge(self, u, v, x=None):


e = Edge(u, v, x)
self._outgoing[u][v] = e
self._outgoing[v][u] = e
return e

def incident_edges(self, v, outgoing=True):


'''
Return all incident edges to node v.
If graph is directed, handle the case of incoming edges
'''
inc = self._outgoing

if v not in inc:
return None

for edge in inc[v].values():


yield edge

def adjacent_vertices(self, v, outgoing=True): # not in book


'''
Return adjacent vertices to a given vertex
'''
if outgoing == True:
if v in self._outgoing:
return self._outgoing[v].keys()
else:
return None
else:
if v in self._incoming:
return self._incoming[v].keys()
else:
return None

def edges_count(self): #not in book


edges = set()
for secondary_map in self._outgoing.values():
edges.update(secondary_map.values())
return len(edges)

def vertex_count(self):
return len(self._outgoing)
def degree(self, v, outgoing=True):
inc = self._outgoing
return len(inc[v])

#-------------Create the actual graph below (G1), with its vertices first,
#------------------then its edges, then print the list of vertices and edges ------

# Create the graph object G1


G1 = Graph(False)
# print('G1 is : ', G1, 'type(G1) is : ', type(G1))

# Create the vertexes a,b,c in the graph G1


u = G1.insert_vertex('u')
v = G1.insert_vertex('v')
w = G1.insert_vertex('w')
z = G1.insert_vertex('z')
print('\n', 'The entered vertices are: ')
for x in G1.vertices():
print(x.element())

# Create the edges (a,b), (a,c), (b,c) in the graph G1


e = G1.insert_edge(u,v,'e')
f = G1.insert_edge(v,w,'f')
g = G1.insert_edge(u,w,'g')
h = G1.insert_edge(w,z,'h')

print('\n', 'The entered edges are: ')


for x in G1.edges():
print(x.element())
# incident_edges() called here
print('\n', 'The incident edges of vertex u are: ')
for x in G1.incident_edges(u):
print(x.element())

print('\n', 'The incident edges of vertex v are: ')


for x in G1.incident_edges(v):
print(x.element())

print('\n', 'The incident edges of vertex w are: ')


for x in G1.incident_edges(w):
print(x.element())

print('\n', 'The incident edges of vertex z are: ')


for x in G1.incident_edges(z):
print(x.element())

# adjacent_vertices() called here


print('\n', 'The adjacent vertices of the u are: ')
for x in G1.adjacent_vertices(u):
print(x.element())
print('\n', 'The adjacent vertices of the v are: ')
for x in G1.adjacent_vertices(v):
print(x.element())
print('\n', 'The adjacent vertices of the w are: ')
for x in G1.adjacent_vertices(w):
print(x.element())
print('\n', 'The adjacent vertices of the z are: ')
for x in G1.adjacent_vertices(z):
print(x.element())

# total no. of vertices in the graph


print('No. of vertixes in G1: ', G1.vertex_count())

# total no. of edges in the graph


print('No. of edges in G1: ', G1.edges_count())

# degree of a node Ex: degree of a is 2


print('degree of u is: ', G1.degree(u))
print('degree of v is: ', G1.degree(v))
print('degree of w is: ', G1.degree(w))
print('degree of z is: ', G1.degree(z))

Result:

Thus the above program was successfully run and the output was verified.
Ex.no.12(b) Graph Representation and Traversal algorithm
Date: (directed graph)

Aim:
The aim of the provided code is to implement a simple directed graph using
classes in Python.

Algorithm :
1. Define the Vertex Class
o Step 1.1: Define a class Vertex to represent graph vertices.
o Step 1.2: The class should store the element (value) of the vertex.
o Step 1.3: Implement the element() method to retrieve the value of the vertex.
o Step 1.4: Implement the hash () method to ensure vertices can be used as
dictionary keys.
2. Define the Edge Class
o Step 2.1: Define a class Edge to represent graph edges, which are directed from one
vertex to another.
o Step 2.2: The class should store the origin vertex, destination vertex, and an element
(value) associated with the edge.
o Step 2.3: Implement the endPoints() method to return the origin and destination of the
edge.
o Step 2.4: Implement the opposite() method to retrieve the opposite vertex of a given
vertex in the edge.
o Step 2.5: Implement the element() method to retrieve the value of the edge.
o Step 2.6: Implement the hash () method to ensure edges can be used as dictionary
keys.
3. Define the Graph Class
o Step 3.1: Define a class Graph to represent the graph structure.
o Step 3.2: The graph should support directed edges (as indicated by the directed=True
argument in the constructor).
o Step 3.3: The graph should store vertices and edges using two dictionaries:
_outgoing dictionary: Stores outgoing edges from a vertex.
_incoming dictionary: Stores incoming edges to a vertex.
o Step 3.4: Implement methods to insert vertices and edges into the graph:
insert_vertex(x): Inserts a new vertex with value x.
insert_edge(u, v, x): Inserts a directed edge from vertex u to vertex v with
value x.
o Step 3.5: Implement methods to access vertices, edges, and counts:
vertices(): Returns the list of all vertices in the graph.
edges(): Returns the list of all edges in the graph.
edges_count(): Returns the total number of edges in the graph.
vertex_count(): Returns the total number of vertices in the graph.
o Step 3.6: Implement methods to get incident edges and adjacent vertices:
incident_edges(v, outgoing=True): Returns the incident edges of vertex v. If
outgoing=True, it returns outgoing edges; otherwise, it returns incoming
edges.
adjacent_vertices(v, outgoing=True): Returns the adjacent vertices to vertex v.
If outgoing=True, it returns vertices connected by outgoing edges; otherwise,
it returns vertices connected by incoming edges.
o Step 3.7: Implement a degree() method to get the degree of a vertex:
degree(v, outgoing=True): Returns the out-degree (if outgoing=True) or in-
degree (if outgoing=False) of vertex v.
4. Graph Creation and Initialization
o Step 4.1: Create a Graph object G1, which is directed (True).
o Step 4.2: Insert vertices into the graph:
Create vertex u, vertex v, vertex w, and vertex z using insert_vertex().
o Step 4.3: Print the list of all vertices in the graph.
5. Edge Creation
o Step 5.1: Insert edges into the graph:
Insert directed edge from vertex u to vertex v with value 'e'.
Insert directed edge from vertex v to vertex w with value 'f'.
Insert directed edge from vertex u to vertex w with value 'g'.
Insert directed edge from vertex w to vertex z with value 'h'.
o Step 5.2: Print the list of all edges in the graph.
6. Incident Edges
o Step 6.1: For each vertex, print its incident edges:
Print the incident edges of vertex u.
Print the incident edges of vertex v.
Print the incident edges of vertex w.
Print the incident edges of vertex z.
7. Adjacent Vertices
o Step 7.1: For each vertex, print its adjacent vertices:
Print the adjacent vertices of vertex u.
Print the adjacent vertices of vertex v.
Print the adjacent vertices of vertex w.
Print the adjacent vertices of vertex z.
8. Counts and Degrees
o Step 8.1: Print the total number of vertices and edges in the graph.
o Step 8.2: For each vertex, print its out-degree and in-degree:
Print the out-degree of vertex u.
Print the out-degree of vertex v.
Print the out-degree of vertex w.
Print the out-degree of vertex z.
o Step 8.3: Print the in-degree of each vertex:
Print the in-degree of vertex u.
Print the in-degree of vertex v.
Print the in-degree of vertex w.
Print the in-degree of vertex z.

Program:
# ------------------------- The Vertex class -------------------------
class Vertex:
slots = '_element'

def init (self, x):


self._element = x

def element(self):
return self._element

def hash (self):


return hash(id(self))

# ------------------------- The Edge class ---------------------------


class Edge:
slots = '_origin', '_destination', '_element'

def init (self, u, v, x):


self._origin = u
self._destination = v
self._element = x

def endPoints(self):
return (self._origin, self._destination)

def opposite(self, v):


return self._destination if self._origin == v else self._origin

def element(self):
return self._element

def hash (self):


return hash(id(self))

# ------------------------- The graph class ---------------------------


class Graph: # This graph can be used as directed graph only. Direction items are
removed.

def init (self, directed=False):


self._outgoing = {}
# This is the Graph Data Structure which holds vertexes and edges
# each item is a key value pair
# key is vertex name
# value for each key is a dictionary - each key:value pair consists of
adjacent_vertex : corresponding outgoing edge
# For this graph, self._outgoing = { a:{b:e1,c:e2}, b:{ab,bc}, c:{ac,bc}}

self._incoming = {} # incoming edges are mentioned here.


def vertices(self):
return self._outgoing.keys()

def edges(self):
edges = set()
for secondary_map in self._outgoing.values():
edges.update(secondary_map.values())
# Update operation avoids duplicate values. Also, set will not contain duplicate
values as well.
return edges

def insert_vertex(self, x=None):


v = Vertex(x)
self._outgoing[v] = {}
self._incoming[v] = {}
return v

def insert_edge(self, u, v, x=None):


e = Edge(u, v, x)
self._outgoing[u][v] = e
self._incoming[v][u] = e
return e

def incident_edges(self, v, outgoing=True):


'''
Return all incident edges to node v.
If graph is directed, handle the case of incoming edges
'''
# inc = self._outgoing if outgoing else self._incoming
if outgoing == True:
inc = self._outgoing
else:
inc = self._incoming

if v not in inc:
return None

for edge in inc[v].values():


yield edge
def adjacent_vertices(self, v, outgoing=True): # not in book
'''
Return adjacent vertices to a given vertex
'''
if outgoing == True:
if v in self._outgoing:
return self._outgoing[v].keys()
else:
return None
else:
if v in self._incoming:
return self._incoming[v].keys()
else:
return None

def edges_count(self): #not in book


edges = set()
for secondary_map in self._outgoing.values():
edges.update(secondary_map.values())
return len(edges)

def vertex_count(self):
return len(self._outgoing)

def degree(self, v, outgoing=True):


if outgoing == True:
inc = self._outgoing
else:
inc = self._incoming

return len(inc[v])

#-------------Create the actual graph below (G1), with its vertices first,
#------------------then its edges, then print the list of vertices and edges ------

# Create the graph object G1


G1 = Graph(True)
# print('G1 is : ', G1, 'type(G1) is : ', type(G1))

# Create the vertexes a,b,c in the graph G1


u = G1.insert_vertex('u')
v = G1.insert_vertex('v')
w = G1.insert_vertex('w')
z = G1.insert_vertex('z')
print('\n', 'The entered vertices are: ')
for x in G1.vertices():
print(x.element())

# Create the edges (a,b), (a,c), (b,c) in the graph G1


e = G1.insert_edge(u,v,'e')
f = G1.insert_edge(v,w,'f')
g = G1.insert_edge(u,w,'g')
h = G1.insert_edge(w,z,'h')

print('\n', 'The entered edges are: ')


for x in G1.edges():
print(x.element())

# incident_edges() called here


print('\n', 'The incident edges of vertex u are: ')
for x in G1.incident_edges(u):
print(x.element())

print('\n', 'The incident edges of vertex v are: ')


for x in G1.incident_edges(v):
print(x.element())

print('\n', 'The incident edges of vertex w are: ')


for x in G1.incident_edges(w):
print(x.element())

print('\n', 'The incident edges of vertex z are: ')


for x in G1.incident_edges(z):
print(x.element())

# adjacent_vertices() called here


print('\n', 'The adjacent vertices of the u are: ')
for x in G1.adjacent_vertices(u):
print(x.element())
print('\n', 'The adjacent vertices of the v are: ')
for x in G1.adjacent_vertices(v):
print(x.element())
print('\n', 'The adjacent vertices of the w are: ')
for x in G1.adjacent_vertices(w):
print(x.element())
print('\n', 'The adjacent vertices of the z are: ')
for x in G1.adjacent_vertices(z):
print(x.element())

# total no. of vertices in the graph


print('No. of vertixes in G1: ', G1.vertex_count())

# total no. of edges in the graph


print('No. of edges in G1: ', G1.edges_count())

# Out-degree of a node Ex: degree of u is 2


print('\n', 'The out-degrees of nodes are: ')
print('degree of u is: ', G1.degree(u))
print('degree of v is: ', G1.degree(v))
print('degree of w is: ', G1.degree(w))
print('degree of z is: ', G1.degree(z))

# in-degree of a node Ex: degree of u is 0


print('\n', 'The in-degrees of nodes are: ')
print('degree of u is: ', G1.degree(u, False))
print('degree of v is: ', G1.degree(v, False))
print('degree of w is: ', G1.degree(w, False))
print('degree of z is: ', G1.degree(z, False))

Result:

Thus the above program was successfully run and the output was verified
Ex.no.13 Implementation of single source shortest path
Date: algorithm

Aim:
The aim of the provided program is to implement Dijkstra's algorithm for
finding the shortest paths from a given source vertex to all other vertices in a
graph.

Algorithm :

1. PriorityQueueBase Class
1. Define the PriorityQueueBase class
o Step 1.1: Create an abstract base class PriorityQueueBase for a priority
queue with the following public methods:
is_empty(): Returns whether the priority queue is empty.
len(): Returns the number of items in the queue (abstract, to be
implemented by subclasses).
add(): Adds a key-value pair to the queue (abstract).
min(): Returns the item with the minimum key without removing it
(abstract).
remove_min(): Removes and returns the item with the minimum
key (abstract).
2. Define the Nested _Item Class
o Step 1.2: Inside PriorityQueueBase, define an internal _Item class with:
_key: Stores the priority key.
_value: Stores the associated value.
lt (): Compares two _Item objects based on the priority key
(used for sorting or ordering).
repr (): Returns a string representation of the item in the format
(key, value).

2. HeapPriorityQueue Class
1. Define the HeapPriorityQueue Class
o Step 2.1: Define HeapPriorityQueue as a subclass of PriorityQueueBase,
which implements a priority queue using a binary heap (min-heap).
2. Implement Private Helper Methods
o Step 2.2: Define helper methods to manage the binary heap structure:
_parent(j): Returns the index of the parent of the node at index j.
_left(j): Returns the index of the left child of the node at index j.
_right(j): Returns the index of the right child of the node at index j.
_has_left(j): Returns True if the node at index j has a left child.
_has_right(j): Returns True if the node at index j has a right child.
_swap(i, j): Swaps the elements at indices i and j in the heap array.
_upheap(j): Moves the element at index j up the heap to maintain
the heap property.
_downheap(j): Moves the element at index j down the heap to
maintain the heap property.
3. Implement Public Methods
o Step 2.3: Implement the following public methods:
init(): Initializes an empty heap with a list self._data.
len(): Returns the number of items in the priority queue (i.e., the
length of self._data).
add(key, value): Adds a key-value pair to the heap, and then moves
the newly added element into the correct position using _upheap().
min(): Returns the (key, value) pair with the minimum key (root of
the heap), or raises an Empty exception if the heap is empty.
remove_min(): Removes and returns the (key, value) pair with the
minimum key (root of the heap), and then restores the heap
property using _downheap().

3. AdaptableHeapPriorityQueue Class
1. Define the AdaptableHeapPriorityQueue Class
o Step 3.1: Define AdaptableHeapPriorityQueue as a subclass of
HeapPriorityQueue, which supports updates and removals of elements in
the priority queue.
2. Define the Nested Locator Class
o Step 3.2: Define a Locator class that inherits from _Item and adds an
index (_index) to track the location of an item in the heap.
3. Override Private Methods
o Step 3.3: Override the _swap() method to update the index in the Locator
objects after a swap.
o Step 3.4: Define the _bubble(j) method to adjust the position of an
element by either bubbling it up or down the heap, depending on whether
its new key is smaller or larger.
4. Implement Public Methods
o Step 3.5: Implement the following public methods:
add(key, value): Adds a key-value pair to the priority queue and
returns a Locator object for the added item.
update(loc, newkey, newval): Updates the key and value for the
item identified by the Locator loc, and then re-adjusts its position
in the heap.
remove(loc): Removes and returns the (key, value) pair identified
by the Locator loc, and restores the heap property.

4. Graph Class
1. Define the Graph Class
oStep 4.1: Define the Graph class that represents an undirected graph. It
includes methods to insert vertices, edges, and query properties like
adjacency, degree, and incident edges.
2. Implement the Graph Methods
o Step 4.2: Implement the following methods:
insert_vertex(x): Adds a vertex with the element x to the graph.
insert_edge(u, v, x): Adds an edge between vertices u and v with
the associated element x.
incident_edges(v): Returns all incident edges of vertex v.
adjacent_vertices(v): Returns all vertices adjacent to vertex v.
edges_count(): Returns the total number of edges in the graph.
vertex_count(): Returns the total number of vertices in the graph.
degree(v): Returns the degree of vertex v (the number of incident
edges).

5. Dijkstra's Algorithm (Dijkstra2)


1. Define the Dijkstra2 Method
o Step 5.1: Implement the Dijkstra2(G, src) method to compute the shortest
paths from a source vertex src to all other vertices in the graph G.
2. Initialize Distance and Cloud
o Step 5.2: Initialize a dictionary d to store the estimated shortest distance
to each vertex. Set the distance to the source vertex as 0 and to all other
vertices as infinity.
o Step 5.3: Initialize a cloud dictionary to keep track of the vertices whose
shortest path has been found.
o Step 5.4: Initialize a priority queue pq (using
AdaptableHeapPriorityQueue), and use it to store vertices with their
distances. Also, create a pqlocator dictionary to track the locators for
vertices in the queue.
3. Main Loop
o Step 5.5: While the priority queue pq is not empty:
Extract the vertex with the minimum distance (key) from the
queue.
Add the vertex to the cloud and remove it from pqlocator.
For each incident edge of the current vertex, check if a shorter path
to the opposite vertex can be found.
If so, update the distance and adjust the position of the vertex in the
priority queue.
4. Return the Result
o Step 5.6: After the algorithm finishes, return the cloud dictionary
containing the shortest distance to each vertex.

6. Example Usage
1. Create the Graph
o Step 6.1: Create a new graph G1 and add vertices a, b, c, d, e, f to it.
2. Add Edges
o Step 6.2: Add edges between the vertices, such as (a, b, 8), (a, c, 2), and
so on.
3. Run Dijkstra's Algorithm
o Step 6.3: Run the Dijkstra2 method with the source vertex a and print the
results.
4. Output the Results
o Step 6.4: Print the shortest distances from the source vertex to all other
vertices.

Program:
class PriorityQueueBase:
"""Abstract base class for a priority queue."""

# ----------------- nested _Item class -----------------


class _Item:
"""Lightweight composite to store priority queue items."""
slots = '_key', '_value'

def init (self, k, v):


self._key = k
self._value = v

def lt (self, other):


return self._key < other._key # compare items based on their keys

def repr (self):


return '({0},{1})'.format(self._key, self._value)

# ----------------- public behaviors -----------------


def is_empty(self): # concrete method assuming abstract len
"""Return True if the priority queue is empty."""
return len(self) == 0

def len (self):


"""Return the number of items in the priority queue."""
raise NotImplementedError('must be implemented by subclass')

def add(self, key, value):


"""Add a key-value pair."""
raise NotImplementedError('must be implemented by subclass')

def min(self):
"""Return but do not remove (k,v) tuple with minimum key.

Raise Empty exception if empty.


"""
raise NotImplementedError('must be implemented by subclass')

def remove_min(self):
"""Remove and return (k,v) tuple with minimum key.

Raise Empty exception if empty.


"""
raise NotImplementedError('must be implemented by subclass')
class HeapPriorityQueue(PriorityQueueBase): # base class defines _Item
"""A min-oriented priority queue implemented with a binary heap."""

# ----------------- nonpublic behaviors -----------------


def _parent(self, j):
return (j - 1) // 2

def _left(self, j):


return 2 * j + 1

def _right(self, j):


return 2 * j + 2

def _has_left(self, j):


return self._left(j) < len(self._data) # index beyond end of list?

def _has_right(self, j):


return self._right(j) < len(self._data) # index beyond end of list?

def _swap(self, i, j):


"""Swap the elements at indices i and j of array."""
self._data[i], self._data[j] = self._data[j], self._data[i]

def _upheap(self, j):


parent = self._parent(j)
if j > 0 and self._data[j] < self._data[parent]:
self._swap(j, parent)
self._upheap(parent) # recur at position of parent

def _downheap(self, j):


if self._has_left(j):
left = self._left(j)
small_child = left # although right may be smaller
if self._has_right(j):
right = self._right(j)
if self._data[right] < self._data[left]:
small_child = right
if self._data[small_child] < self._data[j]:
self._swap(j, small_child)
self._downheap(small_child) # recur at position of small child

# ----------------- public behaviors -----------------


def init (self):
"""Create a new empty Priority Queue."""
self._data = []

def len (self):


"""Return the number of items in the priority queue."""
return len(self._data)

def add(self, key, value):


"""Add a key-value pair to the priority queue."""
self._data.append(self._Item(key, value))
self._upheap(len(self._data) - 1) # upheap newly added position

def min(self):
"""Return but do not remove (k,v) tuple with minimum key.

Raise Empty exception if empty.


"""
if self.is_empty():
raise Empty('Priority queue is empty.')
item = self._data[0]
return (item._key, item._value)

def remove_min(self):
"""Remove and return (k,v) tuple with minimum key.

Raise Empty exception if empty.


"""
if self.is_empty():
raise Empty('Priority queue is empty.')
self._swap(0, len(self._data) - 1) # put minimum item at the end
item = self._data.pop() # and remove it from the list;
self._downheap(0) # then fix new root
return (item._key, item._value)

class AdaptableHeapPriorityQueue(HeapPriorityQueue):
"""A locator-based priority queue implemented with a binary heap."""

# ----------------- nested Locator class -----------------


class Locator(HeapPriorityQueue._Item):
"""Token for locating an entry of the priority queue."""
slots = '_index' # add index as additional field

def init (self, k, v, j):


super().__init (k, v)
self._index = j

# ----------------- nonpublic behaviors -----------------


# override swap to record new indices
def _swap(self, i, j):
super()._swap(i, j) # perform the swap
self._data[i]._index = i # reset locator index (post-swap)
self._data[j]._index = j # reset locator index (post-swap)

def _bubble(self, j):


if j > 0 and self._data[j] < self._data[self._parent(j)]:
self._upheap(j)
else:
self._downheap(j)

# ----------------- public behaviors -----------------


def add(self, key, value):
"""Add a key-value pair."""
token = self.Locator(key, value, len(self._data)) # initiaize locator index
self._data.append(token)
self._upheap(len(self._data) - 1)
return token

def update(self, loc, newkey, newval):


"""Update the key and value for the entry identified by Locator loc."""
j = loc._index
if not (0 <= j < len(self) and self._data[j] is loc):
raise ValueError('Invalid locator')
loc._key = newkey
loc._value = newval
self._bubble(j)

def remove(self, loc):


"""Remove and return the (k,v) pair identified by Locator loc."""
j = loc._index
if not (0 <= j < len(self) and self._data[j] is loc):
raise ValueError('Invalid locator')
if j == len(self) - 1: # item at last position
self._data.pop() # just remove it
else:
self._swap(j, len(self) - 1) # swap item to the last position
self._data.pop() # remove it from the list
self._bubble(j) # fix item displaced by the swap
return (loc._key, loc._value)

# ------------------------- The Vertex class -------------------------

class Vertex:
slots = '_element'

def init (self, x):


self._element = x

def element(self):
return self._element

#----------------------
def lt (self, x):
return self._element < x._element

def hash (self):


return hash(id(self))

# ------------------------- The Edge class ---------------------------


class Edge:
slots = '_origin', '_destination', '_element'

def init (self, u, v, x):


self._origin = u
self._destination = v
self._element = x

def endPoints(self):
return (self._origin, self._destination)

def opposite(self, v):


return self._destination if self._origin == v else self._origin

def element(self):
return self._element
def hash (self):
return hash(id(self))

# ------------------------- The graph class ---------------------------


class Graph: # This graph can be used as directed graph only. Direction items are
removed.

def init (self, directed=False):


self._outgoing = {}

def vertices(self):
return self._outgoing.keys()

def edges(self):
edges = set()
for secondary_map in self._outgoing.values():
edges.update(secondary_map.values())
# Update operation avoids duplicate values. Also, set will not contain duplicate
values as well.
return edges

def insert_vertex(self, x=None):


v = Vertex(x)
self._outgoing[v] = {}
return v

def insert_edge(self, u, v, x=None):


e = Edge(u, v, x)
self._outgoing[u][v] = e
self._outgoing[v][u] = e
return e

def incident_edges(self, v, outgoing=True):


'''
Return all incident edges to node v.
If graph is directed, handle the case of incoming edges
'''
inc = self._outgoing

if v not in inc:
return None

for edge in inc[v].values():


yield edge

def adjacent_vertices(self, v, outgoing=True): # not in book


'''
Return adjacent vertices to a given vertex
'''
if outgoing == True:
if v in self._outgoing:
return self._outgoing[v].keys()
else:
return None
else:
if v in self._incoming:
return self._incoming[v].keys()
else:
return None

def edges_count(self): #not in book


edges = set()
for secondary_map in self._outgoing.values():
edges.update(secondary_map.values())
return len(edges)

def vertex_count(self):
return len(self._outgoing)

def degree(self, v, outgoing=True):


inc = self._outgoing
return len(inc[v])

#------------Dijkstra's algorithm --------------


def Dijkstra2(G, src):
d = {}
cloud = {}
#from queue import PriorityQueue
pq = AdaptableHeapPriorityQueue()
pqlocator = {}

for v in G.vertices():
if v is src:
d[v] = 0
else:
d[v] = 1000
t = (d[v],v)
pqlocator[v] = pq.add(d[v],v)
while pq.is_empty() == False:
#print('length before: ', len(pq))
#[key, u] = pq.get()
key, u = pq.remove_min()
#print('length after: ', len(pq))
#print(pq.get()[0])
print('key is: ', key, ' u is: ', u.element())
cloud[u] = key
del pqlocator[u]

for e in G.incident_edges(u):
v = e.opposite(u)

if v not in cloud:
wgt = int(e.element())
#print(type(wgt))
if (d[u] + wgt) < d[v]: d[v] =
d[u]+wgt
pq.update(pqlocator[v], d[v], v)
# pq.put((d[v],v))

return cloud

#------------Dijkstra's algorithm --------------

#-------------Create the actual graph below (G1), with its vertices first,
#------------------then its edges, then print the list of vertices and edges ------

# Create the graph object G1


G1 = Graph(False)
# print('G1 is : ', G1, 'type(G1) is : ', type(G1))

# Create the vertexes a,b,c in the graph G1


a = G1.insert_vertex('a')
b = G1.insert_vertex('b')
c = G1.insert_vertex('c')
d = G1.insert_vertex('d')
e = G1.insert_vertex('e')
f = G1.insert_vertex('f')
print('\n', 'The entered vertices are: ')
for x in G1.vertices():
print(x.element())

# Create the edges (a,b), (a,c), (b,c) in the graph G1


e1_ab = G1.insert_edge(a,b,'8')
e2_ac = G1.insert_edge(a,c,'2')
e3_ad = G1.insert_edge(a,d,'4')
e4_bc = G1.insert_edge(b,c,'7')
e5_cd = G1.insert_edge(c,d,'1')
e6_be = G1.insert_edge(b,e,'2')
e7_ec = G1.insert_edge(e,c,'3')
e8_cf = G1.insert_edge(c,f,'9')
e9_fd = G1.insert_edge(f,d,'5')

print('\n', 'The entered edges are: ')


for x in G1.edges():
print(x.element())

'''
initials_weights_of_edges = {e1_ab:8, e2_ac:2, e3_ad:4, e4_bc:7, e5_cd:1, e6_be:2,
e7_ec:3, e8_cf:9, e9_fd:5}
dist_est, spt_pred = G1.Dijkstra(initials_weights_of_edges,a)
print('dist_est is: ', dist_est)
print('spt_pred is: ', spt_pred)
'''
c = G1.Dijkstra2(a)
#print('Cloud is: ', c)
#type(c)
for x in c.keys():
print(x.element(), c[x])

Result:

Thus the above program was successfully run and the output was verified
Ex.no.14 IMPLEMENTATION OF MINIMUM SPANNING
TREE ALGORITHMS
Date:
Aim:
The aim of the provided Python code is to implement Prim's algorithm for
finding the Minimum Spanning Tree (MST) of a connected, undirected
graph.

ALGORITHM:

STEP 1) Priority Queue Implementation (HeapPriorityQueue,


AdaptableHeapPriorityQueue):
These classes provide an implementation of a priority queue, which is a
data structure that maintains a set of elements, each associated with a priority,
and supports basic operations such as adding an element, removing the element
with the minimum priority, and updating the priority of an element.

STEP 2) Graph Implementation (Vertex, Edge, Graph):


The Graph class represents an undirected graph using an adjacency map.
Vertices and edges are represented by Vertex and Edge classes, respectively.
The graph supports operations to insert vertices and edges, retrieve
vertices and edges, find incident edges to a vertex, and calculate the number of
vertices and edges.

STEP 3) Prim's Algorithm (MST_PrimJarnik method in Graph class):


This method uses Prim's algorithm to find the Minimum Spanning Tree
of the graph.
It maintains a priority queue (AdaptableHeapPriorityQueue) to keep
track of the minimum distance from each vertex to the growing MST.
The algorithm starts with an arbitrary vertex, initializes the distances to
all other vertices to infinity (or a very large value), and adds vertices to the
priority queue.
In each iteration, it removes the vertex with the minimum distance from
the priority queue, adds the corresponding edge to the MST, and updates the
distances of adjacent vertices if a shorter path is found.
The process continues until all vertices are added to the MST.

STEP 4)
Example Graph and Output:
An example graph (G1) is created with vertices 's', 'a', 'b', 'c', 'd', and 'e', along with
weighted edges.
The MST_PrimJarnik method is called to find the Minimum Spanning Tree.
The edges of the Minimum Spanning Tree are printed as the final output.

PROGRAM:
class PriorityQueueBase:
"""Abstract base class for a priority queue."""
#----------------- nested _Item class----------------
class _Item:
"""Lightweight composite to store priority queue items."""

slots = '_key',
'_value' def init (self,
k, v):
self._key = k
self._value = v
def lt (self, other):
return self._key < other._key # compare items based on their keys
def repr (self):
return '({0},{1})'.format(self._key, self._value)
#----------------- public behaviors----------------
def is_empty(self): # concrete method assuming abstract len
"""Return True if the priority queue is empty."""
return len(self) == 0
def len (self):
"""Return the number of items in the priority queue."""
raise NotImplementedError('must be implemented by subclass')
def add(self, key, value):
"""Add a key-value pair."""
raise NotImplementedError('must be implemented by subclass')
def min(self):
"""Return but do not remove (k,v) tuple with minimum key.
Raise Empty exception if empty.
"""
raise NotImplementedError('must be implemented by subclass')
def remove_min(self):
"""Remove and return (k,v) tuple with minimum key.
Raise Empty exception if empty.
"""
raise NotImplementedError('must be implemented by subclass')
class HeapPriorityQueue(PriorityQueueBase): # base class defines _Item
"""A min-oriented priority queue implemented with a binary heap."""
#----------------- nonpublic behaviors----------------

def _parent(self, j):


return (j- 1) // 2
def _left(self, j):
return 2 * j + 1
def _right(self, j):
return 2 * j + 2
def _has_left(self, j):
return self._left(j) < len(self._data) # index beyond end of list?
def _has_right(self, j):
return self._right(j) < len(self._data) # index beyond end of list?
def _swap(self, i, j):
"""Swap the elements at indices i and j of array."""
self._data[i], self._data[j] = self._data[j], self._data[i]
def _upheap(self, j):
parent = self._parent(j)
if j > 0 and self._data[j] < self._data[parent]:
self._swap(j, parent)
self._upheap(parent) # recur at position of parent
def _downheap(self, j):
if self._has_left(j):
left = self._left(j)
small_child = left # although right may be smaller
if self._has_right(j):
right = self._right(j)
if self._data[right] < self._data[left]:
small_child = right
if self._data[small_child] < self._data[j]:
self._swap(j, small_child)
self._downheap(small_child) # recur at position of small child
#----------------- public behaviors----------------
def init (self):
"""Create a new empty Priority Queue."""
self._data =def len (self):
"""Return the number of items in the priority queue."""
return len(self._data)
def add(self, key, value):
"""Add a key-value pair to the priority queue."""
self._data.append(self._Item(key, value))
self._upheap(len(self._data)- 1) # upheap newly added position
def min(self):
"""Return but do not remove (k,v) tuple with minimum key.
Raise Empty exception if empty.
"""
if self.is_empty():
raise Empty('Priority queue is empty.')
item = self._data[0]
return (item._key, item._value)
def remove_min(self):
"""Remove and return (k,v) tuple with minimum key.
Raise Empty exception if empty.
"""
if self.is_empty():
raise Empty('Priority queue is empty.')
self._swap(0, len(self._data)- 1) # put minimum item at the end
item = self._data.pop() # and remove it from the list;
self._downheap(0) # then fix new root
return (item._key, item._value)
class AdaptableHeapPriorityQueue(HeapPriorityQueue):
"""A locator-based priority queue implemented with a binary heap."""
#----------------- nested Locator class----------------
class Locator(HeapPriorityQueue._Item):
"""Token for locating an entry of the priority queue."""
slots = '_index' # add index as additional field
def init (self, k, v, j):
super(). init (k, v)
self._index = j
#----------------- nonpublic behaviors----------------
# override swap to record new indices
def _swap(self, i, j):
super()._swap(i, j) # perform the swap
self._data[i]._index = i # reset locator index (post-swap)
self._data[j]._index = j # reset locator index (post-swap)
def _bubble(self, j):
if j > 0 and self._data[j] < self._data[self._parent(j)]:
self._upheap(j)
else:

self._downheap(j)
#----------------- public behaviors----------------
def add(self, key, value):
"""Add a key-value pair."""
token = self.Locator(key, value, len(self._data)) # initiaize locator index
self._data.append(token)
self._upheap(len(self._data)- 1)
return token
def update(self, loc, newkey, newval):
"""Update the key and value for the entry identified by Locator loc."""
j = loc._index
if not (0 <= j < len(self) and self._data[j] is loc):
raise ValueError('Invalid locator')
loc._key = newkey
loc._value = newval
self._bubble(j)
def remove(self, loc):
"""Remove and return the (k,v) pair identified by Locator loc."""
j = loc._index
if not (0 <= j < len(self) and self._data[j] is loc):
raise ValueError('Invalid locator')
if j == len(self)- 1: # item at last position
self._data.pop() # just remove it
else:
self._swap(j, len(self)- 1) # swap item to the last position
self._data.pop() # remove it from the list
self._bubble(j) # fix item displaced by the swap
return (loc._key, loc._value)
#------------------------- The Vertex class------------------------
class Vertex:
slots = '_element'
def init (self, x):
self._element = x
def element(self):
return self._element
#---------------------
def lt (self, x):
return self._element < x._element
def hash (self):
return hash(id(self))
#------------------------- The Edge class--------------------------
class Edge:
slots = '_origin', '_destination', '_element'
def init (self, u, v, x):
self._origin = u
self._destination = v
self._element = x
def endPoints(self):
return (self._origin, self._destination)
def opposite(self, v):
return self._destination if self._origin == v else self._origin
def element(self):
return self._element
def hash (self):
return hash(id(self))
#------------------------- The graph class--------------------------
class Graph: # This graph can be used as directed graph only. Direction items are
removed.
def init (self, directed=False):
self._outgoing = {}
def vertices(self):
return self._outgoing.keys()
def edges(self):
edges = set()
for secondary_map in self._outgoing.values():
edges.update(secondary_map.values())
# Update operation avoids duplicate values. Also, set will not contain duplicate values
as
well.
return edges
def insert_vertex(self, x=None):
v =Vertex(x)
self._outgoing[v] = {}
return v
def insert_edge(self, u, v, x=None):
e =Edge(u, v, x)
self._outgoing[u][v] = e
self._outgoing[v][u] = e
return e
def incident_edges(self, v, outgoing=True):
'''
Return all incident edges to node v.
If graph is directed, handle the case of incoming edges
'''
inc = self._outgoing
if v not in inc:
return None
for edge in inc[v].values():
yield edge
def adjacent_vertices(self, v, outgoing=True): # not in book
'''
Return adjacent vertices to a given vertex
'''
if outgoing == True:
if v in self._outgoing:
return self._outgoing[v].keys()
else:
return None
else:
if v in self._incoming:
return self._incoming[v].keys()
else:
return None
def edges_count(self): #not in book
edges = set()
for secondary_map in self._outgoing.values():
edges.update(secondary_map.values())
return len(edges)
def vertex_count(self):
return len(self._outgoing)
def degree(self, v, outgoing=True):
inc = self._outgoing
return len(inc[v])
#------------Dijkstra's algorithm-------------
def Dijkstra2(G, src):
d ={}
cloud = {}
#from queue import PriorityQueue
pq = AdaptableHeapPriorityQueue()
pqlocator = {}
for v in G.vertices():
if v is src:
d[v] = 0
else:
d[v] = 1000
t = (d[v],v)
pqlocator[v] = pq.add(d[v],v)
while pq.is_empty() == False:
#print('length before: ', len(pq))
#[key, u] = pq.get()
key, u = pq.remove_min()
#print('length after: ', len(pq))
#print(pq.get()[0])
print('key is: ', key, ' u is: ', u.element())
cloud[u] = key
del pqlocator[u]
for e in G.incident_edges(u):
v =e.opposite(u)
if v not in cloud:
wgt = int(e.element())
#print(type(wgt))
if (d[u] + wgt) < d[v]: d[v] = d[u]+wgt
pq.update(pqlocator[v], d[v], v)
# pq.put((d[v],v))
return cloud
#------------Dijkstra's algorithm-------------
#-------------Create the actual graph below (G1), with its vertices first,
#------------------then its edges, then print the list of vertices and edges-----
# Create the graph object G1
G1 =Graph(False)
# print('G1 is : ', G1, 'type(G1) is : ', type(G1))
# Create the vertexes a,b,c in the graph G1
a =G1.insert_vertex('a')
b =G1.insert_vertex('b')
c =G1.insert_vertex('c')
d =G1.insert_vertex('d')
e =G1.insert_vertex('e')
f = G1.insert_vertex('f')

print('\n', 'The entered vertices are: ')

for x in G1.vertices():
print(x.element())
# Create the edges (a,b), (a,c), (b,c) in the graph G1
e1_ab = G1.insert_edge(a,b,'8')
e2_ac = G1.insert_edge(a,c,'2')
e3_ad = G1.insert_edge(a,d,'4')
e4_bc = G1.insert_edge(b,c,'7')
e5_cd = G1.insert_edge(c,d,'1')
e6_be = G1.insert_edge(b,e,'2')
e7_ec = G1.insert_edge(e,c,'3')
e8_cf = G1.insert_edge(c,f,'9')
e9_fd = G1.insert_edge(f,d,'5')
print('\n', 'The entered edges are: ')
for x in G1.edges():
print(x.element())
'''
initials_weights_of_edges = {e1_ab:8, e2_ac:2, e3_ad:4, e4_bc:7, e5_cd:1, e6_be:2,
e7_ec:3,
e8_cf:9, e9_fd:5}
dist_est, spt_pred = G1.Dijkstra(initials_weights_of_edges,a)
print('dist_est is: ', dist_est)
print('spt_pred is: ', spt_pred)
'''
c =G1.Dijkstra2(a)
#print('Cloud is: ', c)
#type(c)
for x in c.keys():

print(x.element(), c[x])
RESULT:
Thus the programtoimplement Prim's algorithm for finding the Minimum
Spanning Tree

You might also like