Priority Queues & Heaps
Review: Trees
Binary Search Trees (BSTs):
Implement an ordered mapping ADT if keys are comparable.
Operations include: get, put, floor, ceil, remove, contains.
Running times are proportional to the height of the tree.
Worst-case scenario (unbalanced BST): degrades to a linear structure with O(n) time complexity.
Self-balancing BSTs: height is always O(log n), resulting in O(log n) time complexity for operations.
Topics
Priority Queues
Heaps & Implementation
Building a Heap from a List
Changing Item Priorities
Heapsort
The Priority Queue
Stores a collection of items, each with a priority.
Key operations:
insert(item, priority): Adds an item with a given priority to the collection.peekmin(): Returns the item with the highest priority (ties are broken arbitrarily).removemin(): Removes and returns the item with the highest priority (ties are broken arbitrarily).
Applications: operating systems, job scheduling, and network routers.
Priority Queue Implementation: ListPQ
A basic implementation using a list in Python.
Class
ListPQ:__init__(self): Initializes an empty listself._L.insert(self, n): Appends the elementnto the list.peekMin(self): Returns the minimum element in the list using themin()function.removeMin(self):Finds the minimum element (
minNum) in the list.Removes
minNumfrom the list.Returns
minNum.
The Priority Queue: Running Times
Analyzing the time complexity of
ListPQoperations:insert(): , as it simply appends to the list.peekMin(): , as it requires traversing the list to find the minimum element.removeMin(): , due to themin()andremove()operations, both of which may require traversing the list.
Question: What if we maintained a sorted list?
The Priority Queue: SortedListPQ
An alternative implementation that keeps the list sorted.
Class
SortedListPQ:__init__(self): Initializes an empty listself._L.insert(self, n):Appends the element
nto the list.Sorts the list in reverse order using
self._L.sort(reverse=True). Descending sort places min at the end of list.
peekmin(self): Returns the last element of the list (self._L[-1]), which is the minimum element.removemin(self):Assigns the last element of the list (
self._L[-1]) tominNum.Removes the last element using
self._L.pop().Returns
minNum.
SortedListPQ: Running Times
insert(): , because of the sorting operationself._L.sort()peekMin(): , as it directly accesses the last element.removeMin(): , as it simply pops the last element.
Transition to Heaps
Inefficiency reminder: The
SortedListPQstill has the inefficiency of for insert.Question: can we do better?
Solution: Heaps
Heaps
A (min) heap is a binary tree with two key properties:
Heap-ordered: Each node is smaller than its children, ensuring the smallest node is always the root.
Complete: The tree has minimal height, with leaf nodes in the leftmost positions.
Implications:
Every path from the root to a leaf is sorted in ascending order.
There is no specific relationship mandated between sibling nodes.
Heaps: Operations
Featured operations:
insert(item, priority): Adds an item with a given priority to the collection.peekmin(): Returns the item with the highest priority.removemin(): Removes and returns the item with the highest priority.
Simplified Supported operations:
insert(n): add n to heappeekmin(): return the smallest integerremovemin(): remove and return the smallest integer
Heaps: Implementation Details
Representing a heap using a Python list (array).
Encoding a tree structure in an array: Root at index 0. The left child of an index is at , and the right child is at .
A list is heap-sorted if its corresponding binary tree is heap-sorted.
Implementation: Class HeapPQ
The root is at index 0
The left child of an index is located at
The right child of an index is located at
A list is heap-sorted if its corresponding binary tree is heap-sorted.
__init__(self): Initializes the heap with an empty list called_entries_len_(self): Returns the number of entries in the heap._parent(self, i): Returns the parent index for a given tree node index , calculated as . Integer division//is used._left(self, i): Returns the index of the root of the left child subtree for a given tree node index , calculated as_right(self, i): Returns the index of the root of the right child subtree for a given tree node index , calculated as_children(self, i): Returns an iterable containing only the left and right child subtree root indices for a given index.
Heaps: peekmin
Accessing the minimum element in a min-heap.
peekmin(self): Returns the element at the root of the heap, which isself._entries[0].Running time: , as it is a direct access.
Heaps: insert
Maintaining heap properties during insertion:
Completeness
Heap order
To maintain heap order, swaps may be needed to move the new element up the tree.
Running time: .
Heaps: removemin
Maintaining heap properties during removal:
Completeness
Heap order
The process involves:
Replacing the root (minimum element) with the last element in the heap.
Reducing the size of the heap by one.
"Down-heaping" to restore heap order.
Running time: .
Heaps: Implementation code
removemin(self):: References the internal list of entries.
: Stores the item at the root to be returned.
: Moves the last item in the list to the root.
L.pop(): Removes the last item (which was moved to the root).self._downheap(0): Calls the_downheapmethod to restore heap order starting from the root.return item: Returns the original minimum item.
_downheap(self, i):: References the internal list of entries.
children = self._children(i): Gets the indices of the children of the current node.If
children:min_child = min(children, key = lambda x: L[x]): Finds the child with the smallest key.If
L[i] > L[min_child]:self._swap(i, min_child): Swaps the current node with the smallest child.self._downheap(min_child): Recursively calls_downheapon the swapped child to continue restoring heap order.
Priority Queues: Comparison
Implementation | peekmin() | removemin() | insert(n) |
|---|---|---|---|
ListPQ | O(n) | O(n) | O(1) |
SortedListPQ | O(1) | O(1) | O(n) |
HeapPQ | O(1) | O(log n) | O(log n) |
Building a Heap from a List
Naive approach: insert items one by one into a
HeapPQobject, which takes time.A more efficient approach: Heapifying a list in linear time.
Heapify via Upheap
Heap-ordering a list in-place by upheaping each node from left to right, level by level, resulting in a heap at the end.
Heapify via Downheap
Heap-ordering a list in-place by downheaping each node from right to left, level by level, resulting in a heap at the end.
Heapify via Downheap: Running Time
The
_heapifymethod iterates in reverse through the array and calls the_downheapmethod on each element.def
_heapify(self):for i in reversed (range(len(self))):self._downheap(i)We only need to iterate through the left half of the array in reverse!
A list can be heapified in linear time: .
Change Priority
Given an item in the heap and a new priority:
Change the priority of the entry that holds the item.
Call
upheapordownheapwith the index of the entry to ensure heap-ordering holds after altering priority.
This would only take time.
Implement with a dictionary. This maps items to array indices so that we know where the entry for a given item is located in the array. This dictionary must be updated whenever an item is inserted into the heap or a swap is made, to ensure correctness.
Example:
change priority("Ed", 0)Look up index of βEdβ in item map; find entry.
Change priority of entry
Call upheap on entry
Note that item map must be updated whenever swaps occur.
Heapsort
Given a list of items, we can sort the items using a heap:
Heapify the list β time
Repeatedly remove the minimum element until the heap is empty
The items will be removed in sorted order!
Heapsort is .
Heapsort Implementation Details
Instead of calling
removemin, we can:Swap the min element with the last heap index.
Decrement the internal size of the heap.
Call
downheapon index 0.Repeat until the internal size of the heap is 1.
When using a min heap, this yields a reverse sorted list. Therefore, using this process with a max heap would be faster because we can avoid this reverse operation!
Note: In-place heapsort is because we still call
downheap (0)times. It is also not stable.
Heaps: Summary
Operation | Big-O Running Time |
|---|---|
peekmin (peekmax) | |
removemin | |
(removemax) | |
insert | |
change_priority | |
(need item_map) | |
heapify | |
(using downheap) | |
heapsort |