Heap Sort — Practice Tasks¶
All in Go, Java, Python.
Beginner¶
Task 1: Implement Min-Heap from Scratch¶
class MinHeap:
def __init__(self): self._h = []
def push(self, x): pass # TODO: append + sift-up
def pop(self): pass # TODO: swap root with last, pop, sift-down
def peek(self): return self._h[0]
Task 2: Implement Max-Heap¶
Same as Min-Heap with reversed comparison.
Task 3: Heap Sort (Ascending) using Max-Heap¶
- Test:[5, 2, 8, 1, 9, 3] → [1, 2, 3, 5, 8, 9] Task 4: Heap Sort using Built-in heapq¶
import heapq
def heap_sort_builtin(arr):
h = list(arr)
heapq.heapify(h) # O(n)
return [heapq.heappop(h) for _ in range(len(h))]
Task 5: Iterative siftDown¶
Intermediate¶
Task 6: K-th Smallest Element¶
Time: O(n + k log n).Task 7: K-th Largest Element (Heap of Size K)¶
Time: O(n log k). Better than full sort for k << n.Task 8: Top-K Frequent Elements (LeetCode 347)¶
Task 9: Merge K Sorted Lists (LeetCode 23)¶
Time: O(N log k).Task 10: Priority Queue with Custom Comparator¶
import heapq
def priority_queue_demo():
# TODO: store tuples (priority, item); push/pop in priority order
pass
Advanced¶
Task 11: Dijkstra's Shortest Path¶
import heapq
def dijkstra(graph, start):
# graph: {u: [(v, weight), ...]}
dist = {start: 0}
pq = [(0, start)]
# TODO: pop min-distance node, relax edges, push updated distances
return dist
Task 12: Huffman Coding¶
import heapq
def huffman_codes(freqs):
# freqs: {char: count}
# TODO: build huffman tree using min-heap
return {}
Task 13: Sliding Window Maximum (LeetCode 239)¶
Heap-based version (deque is faster but heap is the simpler conceptual fit):
import heapq
def max_sliding_window_heap(nums, k):
# TODO: max-heap (negate values); lazy deletion of out-of-window items
pass
Task 14: Median Maintenance (Two Heaps)¶
import heapq
class MedianFinder:
def __init__(self):
self.lo = [] # max-heap (negate)
self.hi = [] # min-heap
def add(self, num): pass # TODO
def median(self): pass # O(1)
Task 15: Indexed Min-Heap (Decrease-Key Support)¶
class IndexedMinHeap:
"""Supports decrease_key(item, new_priority) in O(log n)."""
def __init__(self):
self._h = []
self._idx = {} # item → position in _h
def push(self, item, priority): pass # TODO
def pop(self): pass
def decrease_key(self, item, new_priority): pass
Benchmark¶
| n | Heap Sort | Quick Sort (Pdqsort) | Merge Sort | TimSort |
|---|---|---|---|---|
| 1,000 | 0.3 ms | 0.05 ms | 0.1 ms | 0.05 ms |
| 10,000 | 4 ms | 0.5 ms | 1.2 ms | 0.8 ms |
| 100,000 | 50 ms | 5 ms | 12 ms | 8 ms |
Heap Sort is 10× slower than Pdqsort because: - Heap traversal is cache-unfriendly (parent/child indices jump in memory). - More comparisons per element (~2 log n vs ~1.05 log n for Pdqsort).
Heap Sort wins when: O(1) extra space + O(n log n) worst case both required (e.g., Introsort fallback).
Self-Assessment¶
| Skill | Beginner | Intermediate | Advanced |
|---|---|---|---|
| Min-heap & max-heap | Required | — | — |
| Heap sort | Required | — | — |
| Top-K with heap | — | Required | — |
| Merge K sorted lists | — | Required | — |
| Dijkstra | — | — | Required |
| Indexed heap (decrease-key) | — | — | Required |
| Median maintenance | — | — | Required |