19. Heap Sort
661 words
3 min read
Visual companion
Python
Type and operator map
Python Week 1: the first filter for runtime behavior
View
Revision summary
What this note is really saying
Short form
# 19. Heap Sort > **What problem does this solve?** Heap sort gives guaranteed O(n log n) time with only O(1) extra space — combining the guarantees of merge sort with the in-place nature of quick sort.

19. Heap Sort
What problem does this solve? Heap sort gives guaranteed O(n log n) time with only O(1) extra space — combining the guarantees of merge sort with the in-place nature of quick sort.
1. How Heap Sort Works
- Build max-heap from the array (O(n))
- Repeatedly extract the maximum and place it at the end (O(n log n)) (Diagram)
2. Implementation
python# runnable def heap_sort(arr): """Sort arr in-place using heap sort. Time: O(n log n) — best, average, worst Space: O(1) — in-place Not stable """ n = len(arr) # Phase 1: Build max-heap (Floyd's algorithm) for i in range(n // 2 - 1, -1, -1): _sift_down(arr, n, i) # Phase 2: Extract max repeatedly for i in range(n - 1, 0, -1): arr[0], arr[i] = arr[i], arr[0] # Swap max to end _sift_down(arr, i, 0) # Restore heap property return arr def _sift_down(arr, n, i): """Sift down element at index i in max-heap of size n.""" largest = i left = 2 * i + 1 right = 2 * i + 2 if left < n and arr[left] > arr[largest]: largest = left if right < n and arr[right] > arr[largest]: largest = right if largest != i: arr[i], arr[largest] = arr[largest], arr[i] _sift_down(arr, n, largest) # Test arr = [12, 11, 13, 5, 6, 7] print(f"Original: {arr}") heap_sort(arr) print(f"Sorted: {arr}") # [5, 6, 7, 11, 12, 13]
3. Complete Trace
Sorting [4, 10, 3, 5, 1]:
Phase 1: Build Max-Heap
pseudoInitial: [4, 10, 3, 5, 1] n = 5, last non-leaf = n//2 - 1 = 1 i=1 (val=10): children 5, 1. 10 already largest → [4, 10, 3, 5, 1] i=0 (val=4): children 10, 3. Largest = 1 → swap 4↔10 → [10, 4, 3, 5, 1] Now i=1 (val=4): children 5, 1. Largest = 3 → swap 4↔5 → [10, 5, 3, 4, 1]
Phase 2: Extract Max
pseudoSwap root(10) with last(1): [1, 5, 3, 4, 10] Sift-down 1: children 5,3 → swap 1↔5 → [5, 1, 3, 4, 10] Sift-down 1: children 4 → swap 1↔4 → [5, 4, 3, 1, 10] Swap root(5) with last(1): [1, 4, 3, 5, 10] Sift-down 1: children 4,3 → swap 1↔4 → [4, 1, 3, 5, 10] Sift-down 1: children 3 → OK → [4, 1, 3, 5, 10] Swap root(4) with last(3): [3, 1, 4, 5, 10] Sift-down 3: children 1 → OK → [3, 1, 4, 5, 10] Swap root(3) with last(1): [1, 3, 4, 5, 10] Sift-down 1: children → OK → [1, 3, 4, 5, 10] Sorted! [1, 3, 4, 5, 10]
4. Performance Comparison
| Aspect | Heap Sort | Merge Sort | Quick Sort |
|---|---|---|---|
| Best | O(n log n) | O(n log n) | O(n log n) |
| Average | O(n log n) | O(n log n) | O(n log n) |
| Worst | O(n log n) ★ | O(n log n) | O(n²) |
| Space | O(1) ★ | O(n) | O(log n) |
| Stable | No | Yes | No |
| Cache | Poor (jumps around) | Good (sequential) | Good |
Practice Questions
Q1. Trace heap sort on [3, 1, 6, 5, 2, 4].
Q2. Why is heap sort not stable?
Q3. Compare the constants: heap sort vs merge sort for n = 10⁶.
Q4. Can heap sort be adapted to sort in descending order? How?
AnswersA1. Build heap: [6, 5, 4, 1, 2, 3]. Extract: [5, 3, 4, 1, 2, 6] → [4, 3, 2, 1, 5, 6] → [3, 1, 2, 4, 5, 6] → [2, 1, 3, 4, 5, 6] → [1, 2, 3, 4, 5, 6].A2. Long-distance swaps (root with last element) can reorder equal elements arbitrarily.A3. Heap sort has ~2× the constant of merge sort due to more comparisons and poor cache behavior. Merge sort is preferred when guaranteed O(n log n) and O(n) space is acceptable.A4. Use a min-heap instead of max-heap. Extract min repeatedly → descending order. Join Discord Previous18. Heaps & Priority QueuesNext20. Graph Representations — Adjacency Matrix & List