Practical 2
Class Stack:
Def __init__(self):
# Initialize an empty stack
Self.stack = []
Def push(self, value):
# Push an element onto the stack
Self.stack.append(value)
Print(f”Element {value} pushed onto stack.”)
Def pop(self):
# Pop an element from the stack
If self.is_empty():
Print(“Stack is empty. Cannot pop.”)
Else:
Popped_value = self.stack.pop()
Print(f”Element {popped_value} popped from stack.”)
Return popped_value
Def peek(self):
# Peek at the top element without removing it
If self.is_empty():
Print(“Stack is empty. No element to peek.”)
Else:
Print(f”Top element is: {self.stack[-1]}”)
Return self.stack[-1]
Def is_empty(self):
# Check if the stack is empty
Return len(self.stack) == 0
Def size(self):
# Get the current size of the stack
Return len(self.stack)
# Test the stack implementation
If __name__ == “__main__”:
S = Stack()
s.push(10)
s.push(20)
s.push(30)
s.peek() # Should display 30
s.pop() # Should pop 30
s.pop() # Should pop 20
print(f”Is the stack empty? {‘Yes’ if s.is_empty() else ‘No’}”)
s.pop() # Should pop 10
print(f”Is the stack empty? {‘Yes’ if s.is_empty() else ‘No’}”)
Output :
Element 10 pushed onto stack.
Element 20 pushed onto stack.
Element 30 pushed onto stack.
Top element is: 30
Element 30 popped from stack.
Element 20 popped from stack.
Is the stack empty? No
Element 10 popped from stack.
Is the stack empty? Yes
Practical 9
Class Node:
Def __init__(self, key):
Self.left = None
Self.right = None
Self.value = key
Class BinarySearchTree:
Def __init__(self):
Self.root = None
Def insert(self, root, key):
# If the tree is empty, return a new node
If root is None:
Return Node(key)
# Otherwise, recur down the tree
If key < root.value:
Root.left = self.insert(root.left, key)
Else:
Root.right = self.insert(root.right, key)
Return root
Def search(self, root, key):
# Base case: root is null or key is present at the root
If root is None or root.value == key:
Return root
# Key is greater than root’s value
If key > root.value:
Return self.search(root.right, key)
# Key is smaller than root’s value
Return self.search(root.left, key)
Def inorder(self, root):
# In-order traversal: Left, Root, Right
If root:
Self.inorder(root.left)
Print(root.value, end=’ ‘)
Self.inorder(root.right)
Def preorder(self, root):
# Pre-order traversal: Root, Left, Right
If root:
Print(root.value, end=’ ‘)
Self.preorder(root.left)
Self.preorder(root.right)
Def postorder(self, root):
# Post-order traversal: Left, Right, Root
If root:
Self.postorder(root.left)
Self.postorder(root.right)
Print(root.value, end=’ ‘)
Def min_value_node(self, root):
# Get the node with the minimum value (leftmost leaf)
Current = root
While current.left is not None:
Current = current.left
Return current
Def delete(self, root, key):
# Base case: If the tree is empty
If root is None:
Return root
# Find the node to be deleted
If key < root.value:
Root.left = self.delete(root.left, key)
Elif key > root.value:
Root.right = self.delete(root.right, key)
Else:
# Node with only one child or no child
If root.left is None:
Return root.right
Elif root.right is None:
Return root.left
# Node with two children: Get the inorder successor (smallest in the
right subtree)
Temp = self.min_value_node(root.right)
# Copy the inorder successor’s content to this node
Root.value = temp.value
# Delete the inorder successor
Root.right = self.delete(root.right, temp.value)
Return root
# Test the Binary Search Tree
If __name__ == “__main__”:
Bst = BinarySearchTree()
# Inserting elements
Bst.root = bst.insert(bst.root, 50)
Bst.root = bst.insert(bst.root, 30)
Bst.root = bst.insert(bst.root, 20)
Bst.root = bst.insert(bst.root, 40)
Bst.root = bst.insert(bst.root, 70)
Bst.root = bst.insert(bst.root, 60)
Bst.root = bst.insert(bst.root, 80)
Print(“In-order Traversal:”)
Bst.inorder(bst.root)
Print()
Print(“Pre-order Traversal:”)
Bst.preorder(bst.root)
Print()
Print(“Post-order Traversal:”)
Bst.postorder(bst.root)
Print()
# Searching for a value in the tree
Search_key = 40
Result = bst.search(bst.root, search_key)
If result:
Print(f”Node with value {search_key} found.”)
Else:
Print(f”Node with value {search_key} not found.”)
# Deleting a node
Delete_key = 20
Print(f”\nDeleting node with value {delete_key}:”)
Bst.root = bst.delete(bst.root, delete_key)
Print(“In-order Traversal after deletion:”)
Bst.inorder(bst.root)
Print()
# Deleting a node with two children
Delete_key = 30
Print(f”\nDeleting node with value {delete_key}:”)
Bst.root = bst.delete(bst.root, delete_key)
Print(“In-order Traversal after deletion:”)
Bst.inorder(bst.root)
Print()
Output :
In-order Traversal:
20 30 40 50 60 70 80
Pre-order Traversal:
50 30 20 40 70 60 80
Post-order Traversal:
20 40 30 60 80 70 50
Node with value 40 found.
Deleting node with value 20:
In-order Traversal after deletion:
30 40 50 60 70 80
Deleting node with value 30:
In-order Traversal after deletion:
40 50 60 70 80
Practical 10
Import heapq
Class Graph:
Def __init__(self, vertices):
Self.V = vertices
Self.graph = {i: [] for I in range(vertices)}
Def add_edge(self, u, v, weight):
Self.graph[u].append((v, weight))
Self.graph[v].append((u, weight))
Def prims_mst(self):
Visited = [False] * self.V
Min_heap = [(0, 0)] # (weight, node), starting from node 0
Mst_weight = 0
Mst_edges = []
While min_heap:
Weight, u = heapq.heappop(min_heap)
If visited[u]:
Continue
Visited[u] = True
Mst_weight += weight
For v, w in self.graph[u]:
If not visited[v]:
Heapq.heappush(min_heap, (w, v))
If weight != 0: # Ignore the starting node edge with weight 0
Mst_edges.append((u, weight))
Print(“Edges in MST:”)
For edge in mst_edges:
Print(edge)
Print(f”Total weight of MST: {mst_weight}”)
# Example usage
If __name__ == “__main__”:
G = Graph(5)
g.add_edge(0, 1, 2)
g.add_edge(0, 3, 6)
g.add_edge(1, 3, 8)
g.add_edge(1, 2, 3)
g.add_edge(1, 4, 5)
g.add_edge(2, 4, 7)
g.prims_mst()
Output:
Edges in MST:
(1, 2)
(2, 3)
(4, 5)
(3, 6)
Total weight of MST: 16