Heaps¶
A Heap is a complete binary tree that satisfies the heap property. Heaps are commonly used to implement priority queues.
Overview¶
py-ds-academy provides two heap implementations:
- MinHeap - Parent nodes are always less than or equal to their children
- MaxHeap - Parent nodes are always greater than or equal to their children
Heap Property¶
MinHeap Property¶
For every node i (except root):
- parent(i) ≤ node(i)
- Smallest element is always at the root
MaxHeap Property¶
For every node i (except root):
- parent(i) ≥ node(i)
- Largest element is always at the root
Operations¶
Creating a Heap¶
from py_ds import MinHeap, MaxHeap
# Create an empty heap
min_heap = MinHeap()
max_heap = MaxHeap()
# Create a heap from an iterable
min_heap = MinHeap([3, 1, 4, 1, 5])
max_heap = MaxHeap([3, 1, 4, 1, 5])
Adding Elements¶
Removing Elements¶
# Pop and return the root (min for MinHeap, max for MaxHeap)
min_item = min_heap.pop() # O(log n)
max_item = max_heap.pop() # O(log n)
Accessing Elements¶
# Peek at the root without removing
min_val = min_heap.peek() # O(1)
max_val = max_heap.peek() # O(1)
Utility Methods¶
# Check if heap is empty
is_empty = min_heap.is_empty() # O(1)
# Get the number of elements
length = len(min_heap) # O(1)
# Convert to list (not sorted!)
items = min_heap.to_list() # O(n)
Time Complexity¶
| Operation | Time Complexity |
|---|---|
push(item) |
O(log n) |
pop() |
O(log n) |
peek() |
O(1) |
is_empty() |
O(1) |
__len__() |
O(1) |
to_list() |
O(n) |
| Construction from iterable | O(n) |
Space Complexity¶
O(n) where n is the number of elements.
Use Cases¶
- Priority Queues - Task scheduling, event simulation
- Heap Sort - Efficient sorting algorithm
- Top-K Problems - Finding k largest/smallest elements
- Median Finding - Using two heaps
- Dijkstra's Algorithm - Shortest path finding
Example: Priority Queue¶
class Task:
def __init__(self, priority, name):
self.priority = priority
self.name = name
def __lt__(self, other):
return self.priority < other.priority
def __repr__(self):
return f"Task({self.priority}, {self.name})"
# MinHeap for priority queue (lower number = higher priority)
pq = MinHeap([
Task(3, "Low priority"),
Task(1, "High priority"),
Task(2, "Medium priority")
])
# Process tasks in priority order
while not pq.is_empty():
task = pq.pop()
print(f"Processing: {task.name}")
Example: Top-K Elements¶
def find_top_k_largest(items, k):
"""Find k largest elements using MinHeap."""
min_heap = MinHeap()
for item in items:
if len(min_heap) < k:
min_heap.push(item)
elif item > min_heap.peek():
min_heap.pop()
min_heap.push(item)
# Return in descending order
result = []
while not min_heap.is_empty():
result.append(min_heap.pop())
return result[::-1]
top_3 = find_top_k_largest([1, 5, 3, 9, 2, 7, 4, 8], 3)
# Returns [9, 8, 7]
Example: Heap Sort¶
def heap_sort(items):
"""Sort items using heap."""
heap = MinHeap(items)
result = []
while not heap.is_empty():
result.append(heap.pop())
return result
sorted_items = heap_sort([3, 1, 4, 1, 5, 9, 2, 6])
# Returns [1, 1, 2, 3, 4, 5, 6, 9]
Example: Finding Median¶
class MedianFinder:
def __init__(self):
# MaxHeap for lower half (largest at root)
self.lower = MaxHeap()
# MinHeap for upper half (smallest at root)
self.upper = MinHeap()
def add(self, num):
if self.lower.is_empty() or num <= self.lower.peek():
self.lower.push(num)
else:
self.upper.push(num)
# Balance heaps
if len(self.lower) > len(self.upper) + 1:
self.upper.push(self.lower.pop())
elif len(self.upper) > len(self.lower) + 1:
self.lower.push(self.upper.pop())
def find_median(self):
if len(self.lower) == len(self.upper):
return (self.lower.peek() + self.upper.peek()) / 2
elif len(self.lower) > len(self.upper):
return self.lower.peek()
else:
return self.upper.peek()
Internal Structure¶
Heaps are typically implemented using arrays:
- Parent of node at index
i:(i - 1) // 2 - Left child of node at index
i:2 * i + 1 - Right child of node at index
i:2 * i + 2
Heap Operations¶
Heapify Up (Bubble Up)¶
After inserting, move element up until heap property is satisfied:
def heapify_up(heap, index):
parent = (index - 1) // 2
if parent >= 0 and heap[parent] > heap[index]:
heap[parent], heap[index] = heap[index], heap[parent]
heapify_up(heap, parent)
Heapify Down (Bubble Down)¶
After removing root, move element down until heap property is satisfied:
def heapify_down(heap, index):
left = 2 * index + 1
right = 2 * index + 2
smallest = index
if left < len(heap) and heap[left] < heap[smallest]:
smallest = left
if right < len(heap) and heap[right] < heap[smallest]:
smallest = right
if smallest != index:
heap[index], heap[smallest] = heap[smallest], heap[index]
heapify_down(heap, smallest)