Lecture Notes on 06 Nov 2023 * Two more traversals - depth first search (dfs) and breadth first search (bfs) * The algorithms for the two searchs are given below: Depth First Search (DFS) 1. Create a Stack 2. Add the root to the Stack 3. If the Stack is not empty pop the node from the Stack and make it the current node. 4. If the current node is not None print the data and add the right child and then the left child of the current node to the Stack. 5. Repeat steps 3 and 4 until the Stack is empty. Breadth First Search (BFS) 1. Create a Queue 2. Add the root to the Queue 3. If the Queue is not empty remove a node from the Queue and make it the current node. 4. If the current node is not None print the data and add the left child and then the right child of the current node to the Queue. 5. Repeat steps 3 and 4 until Queue is empty. Balancing a Tree * Balance Factor = Height of left sub-tree - Height of right sub-tree * In a balanced tree the balance factor of every node is either -1, 0, 1 * AVL Trees https://www.cs.usfca.edu/~galles/visualization/AVLtree.html * AVL Trees Notes https://www.cs.utexas.edu/users/mitra/csFall2023/cs313/notes/AVL_Trees.pdf * Notes on Heaps https://www.cs.utexas.edu/users/mitra/csFall2023/cs313/notes/Heaps.pdf * Code for Heaps class Heap (object): def __init__ (self): self.heap = [] # return the size of the heap def get_size (self): return len (self.heap) # return if the heap is empty def is_empty (self): return (len(self.heap) == 0) # get index of parent def parent_idx (self, idx): new_idx = (idx - 1) // 2 return new_idx # get index of left child def lchild_idx (self, idx): new_idx = 2 * idx + 1 if (new_idx >= len(self.heap): new_idx = -1 return new_idx # get index of right child def rchild_idx (self, idx): new_idx = 2 * idx + 2 if (new_idx >= len(self.heap)): new_idx = -1 return new_idx # move a node up from the bottom to its rightful place def percolate_up (self, idx): node_val = self.heap[idx] parent = self.parent_idx (idx) parent_val = self.heap[parent] while (idx > 0) and (parent_val < node_val): self.heap[idx] = parent_val idx = parent parent = self.parent_idx (idx) parent_val = self.heap[parent] self.heap[idx] = node_val # move a node down from the top to its rightful place def percolate_down (self, idx): node_val = self.heap[idx] num_elements= self.get_size () while (idx < num_elements // 2): left_idx = self.lchild_idx (idx) right_idx = self.rchild_idx (idx) if ((right_idx > -1) and (self.heap[left_idx] < self.heap[right_idx])) larger_child = right_idx else: larger_child = left_idx if (node_val >= self.heap[larger_child]): break self.heap[idx] = self.heap[larger_child] idx = larger_child self.heap[idx] = node_val # insert an element in then ehap def insert (self, val): self.heap.append (val) new_pos = self.get_size() - 1 self.percolate_up (new_pos) # delete the root and remake the heap def remove (self): root = self.heap[0] self.heap[0] = self.heap[-1] self.heap.pop() self.percolate_down (0) return root # given an arbitrary list make a heap out of it def make_heap (self, idx): num_elements = self.get_size() if (idx > ((num_elements // 2) - 1)): return right_idx = self.rchild_idx (idx) if (right_idx > -1): self.make_heap (right_idx): left_idx = self.lchild_idx (idx) self.make_heap (left_idx) self.percolate_down (idx)