Dsa Observation
Dsa Observation
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
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
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
Algorithm infix_to_prefix(input_string)
Input: input_string - string containing infix expression
Output: output_string - string containing prefix expression
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 = ""
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))') )
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:
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:
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:
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`.
class MapBas(MutableMapping):
class _Item:
slots = '_key', '_value'
"""
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
# 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);
"""
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
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]
""" 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'
"""
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
# 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);
"""
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
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
""" 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)
for j in T1._table:
if ( j is not None):
print(j._key, j._value)
else:
print(str(type(j)))
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)`
Program:
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')
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)
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)
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')
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)
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:
if node._left != None:
raise ValueError('Left Child already exists')
self._size = self._size + 1
node._left = self._Node(e, node)
return node._left
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.
# 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.
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)
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)
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)
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:
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
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
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.
# 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.
T = LinkedBinaryTree()
print("_root is : ", T._root, " _size is : ", T._size)
print('')
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.
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():
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 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)
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()
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()
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 element(self):
return self._element
def endPoints(self):
return (self._origin, self._destination)
def element(self):
return self._element
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
if v not in inc:
return None
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 ------
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 element(self):
return self._element
def endPoints(self):
return (self._origin, self._destination)
def element(self):
return self._element
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
if v not in inc:
return None
def vertex_count(self):
return len(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 ------
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).
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."""
def min(self):
"""Return but do not remove (k,v) tuple with minimum key.
def remove_min(self):
"""Remove and return (k,v) tuple with minimum key.
def min(self):
"""Return but do not remove (k,v) tuple with minimum key.
def remove_min(self):
"""Remove and return (k,v) tuple with minimum key.
class AdaptableHeapPriorityQueue(HeapPriorityQueue):
"""A locator-based priority queue implemented with a binary heap."""
class Vertex:
slots = '_element'
def element(self):
return self._element
#----------------------
def lt (self, x):
return self._element < x._element
def endPoints(self):
return (self._origin, self._destination)
def element(self):
return self._element
def hash (self):
return hash(id(self))
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
if v not in inc:
return None
def vertex_count(self):
return len(self._outgoing)
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
#-------------Create the actual graph below (G1), with its vertices first,
#------------------then its edges, then print the list of vertices and edges ------
'''
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 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----------------
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')
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