""" heap_v2.py a implementation of a priority queue as a binary heap including heapsort version 2 : * in class on Thu March 24 * ... and it works! (After some debug printing to get the kinks out) * See the end of these comments for the output. See https://runestone.academy/runestone/books/published/pythonds/Trees/BinaryHeapImplementation.html We'll do at least these operations. - create - add an element - remve an element The heap stores these indeces 0 1 2 3 4 5 6 as these places in a binary tree 0 1 2 3 4 5 6 and we can find the index of parents and children with these formulas. left_child 2*i + 1 (for parent at i) right_child 2*i + 2 parent (n-1)//2 (for child at n) check : (5 - 1) // 2 => 2 (6 - 1) // 2 = 5 //2 = round_down(2.5) => 2 ------------------------------------------------------- $ python heap_v2.py -- heap initial test case as tree -- 5 9 14 18 11 -- heap after inserting 7 -- 5 9 14 18 7 11 -- heap after inserting 3 -- 3 9 14 18 5 11 7 -- pulled out 3 -- heap now is -- 5 9 14 18 7 11 random values are [10, 20, 5, 3, 2, 15, 17, 9] sorted values are [2, 3, 5, 9, 10, 15, 17, 20] Jim Mahoney | cs.bennington.college | MIT License | March 2022 """ infinity = float('inf') # a cute python trick to have a big number # -- utility functions -- def parent(i): """ return the index of the parent of node with index i """ # Remember that // is the "integer division" operator in python. return (i-1) // 2 def left(i): """ return index of the left child of the node with index i """ return 2*i + 1 def right(i): """ return index of the left child of the node with index i """ return 2*i + 2 # -- utility function tests -- # The binary tree indices are arranged like this : # 0 # 1 2 # 3 4 5 6 # ... so here are a few of the index relationships. assert parent(3) == 1 assert parent(6) == 2 assert right(1) == 4 assert left(2) == 5 class Heap: """ An implementation of a minimum priority queue with a binary heap """ def __init__(self): self.array = [] def init_test_case(self): # a valid heap that we can play with; # same numbers as in the pythonds material. # 5 # 9 11 # 14 18 self.array = [5, 9, 11, 14, 18] def valid_index(self, i): """ True iff i is a valid index """ return 0 <= i and i < len(self.array) def tree_string(self, i=0, indent=0): """ return binary heap as a tree string, indented values by depth """ padding = 3 # amount of whitespace to indent each next depth if not self.valid_index(i): return '' else: result = ' ' * indent + str(self.array[i]) + '\n' for child in (left(i), right(i)): if self.valid_index(child): result += self.tree_string(child, indent + padding) return result def swap(self, i , j): """ swap the two values at i,j """ # (a, b) = (b, a) (self.array[i], self.array[j]) = (self.array[j], self.array[i]) def insert(self, value): """ add a new priority value to the heap """ # step 1 : use python's list.append to put on the end of the list self.array.append(value) # step 2 : find the index position of that i = len(self.array) - 1 # index of that new value # step 3: swap with parent if needed, and loop moving upwards while i > 0: i_parent = parent(i) if self.array[i_parent] <= self.array[i]: return else: self.swap(i, i_parent) i = i_parent # move upwards to parent position def get_value_or_inf(self, i): """ return value at index i or infinity if i is invalid """ if i < len(self.array): return self.array[i] else: return infinity def is_empty(self): """ True if no elements """ return len(self.array) == 0 def pull(self): """ remove and return the smallest value from the heap """ # step 1: remember the smallest if self.is_empty(): return None smallest = self.array[0] if len(self.array) == 1: # if that was the last one, self.array.pop() return smallest # just return it. # step 2: move the last value to the first index position #print(" !!! DEBUG !!!") #print(" self.array = ", self.array) self.array[0] = self.array.pop() # step 3: percolate this top node downwards i = 0 # start working on this node n = len(self.array) while i < n: # ... reshape the heap # which child is smaller? i_left = left(i) i_left_value = self.get_value_or_inf(i_left) i_right = right(i) i_right_value = self.get_value_or_inf(i_right) if i_left_value < i_right_value: i_child = i_left i_child_value = i_left_value else: i_child = i_right i_child_value = i_right_value # Should we push this value downwards? if self.array[i] < i_child_value: return smallest else: self.swap(i, i_child) i = i_child return smallest def heapsort(values): """ use this heap to sort values! """ heap = Heap() for value in values: # put each value into the list #print(" DEBUG: value is ", value) heap.insert(value) # O(log n) #print(" DEBUG: heap after value is ") #print(heap.tree_string()) #print(" DEBUG: heap in heapsort is ") #print(heap.tree_string()) result = [] while not heap.is_empty(): # O( n * log(n) ) result.append(heap.pull()) # O(log n) return result def main(): heap = Heap() heap.init_test_case() print("-- heap initial test case as tree --") print(heap.tree_string()) # add a 7 to the heap heap.insert(7) print("-- heap after inserting 7 --") print(heap.tree_string()) # add a 3 to the heap heap.insert(3) print("-- heap after inserting 3 --") print(heap.tree_string()) value = heap.pull() print("-- pulled out ", value) print("-- heap now is --") print(heap.tree_string()) values = [10, 20, 5, 3, 2, 15, 17, 9] print("random values are ", values) sorted_values = heapsort(values) print("sorted values are ", sorted_values) main()