Phase 2 · Searching & SortingModule 9~38 min read

Heap Sort & Linear-Time Sorts

Sort with a heap, then break the O(n log n) barrier with counting, radix, and bucket sort — when the data allows.

What you'll learn

One more comparison sort — heap sort — guarantees O(n log n) with O(1) extra space. Then we cheat: counting, radix, and bucket sort avoid comparisons entirely and run in linear time when the data cooperates.

By the end you'll be able to:

  • Explain how heap sort uses a heap to sort in place
  • State why comparison sorts can't beat Ω(n log n)
  • Use counting, radix, and bucket sort — and know their limits

Heap sort

Treat the array as a binary max-heap: build it so the largest element is at the front, then repeatedly swap that max to the end and sift the heap back into shape over the shrinking front. The sorted region grows from the right:

Heap sort
Heap sort
5
2
8
1
9
3
7
4
1/37Heap sort: build a max-heap in the array, then repeatedly move the max to the end.
Build a max-heap, then extract the max to the end each round while the heap shrinks.
Language
heap_sort.py
def heap_sort(a):
    n = len(a)
    def sift_down(lo, hi):
        root = lo
        while 2 * root + 1 <= hi:
            child = 2 * root + 1
            if child + 1 <= hi and a[child] < a[child + 1]:
                child += 1
            if a[root] < a[child]:
                a[root], a[child] = a[child], a[root]
                root = child
            else:
                break
    for i in range(n // 2 - 1, -1, -1):   # build max-heap
        sift_down(i, n - 1)
    for hi in range(n - 1, 0, -1):        # extract max
        a[0], a[hi] = a[hi], a[0]
        sift_down(0, hi - 1)
    return a

Note

Heap sort matches merge sort's worst-case O(n log n) but sorts in place. Its downside is poor cache behavior (jumping around the array) and it's not stable — so quick sort usually wins in practice.

The comparison barrier

Any sort that only compares elements must make at least Ω(n log n) comparisons in the worst case. The reason: there are n! possible orderings, and each comparison (a yes/no) can at best halve the possibilities, so you need log₂(n!) ≈ n log n comparisons to pin down the right one.

Key idea

This lower bound only applies to comparison sorts. To go faster we must stop comparing and start using the values themselves as indexes — that's how the next sorts break the barrier.

Counting sort

If the values are small integers in a known range 0..k, don't compare — tally. Count how many times each value appears, then read the counts back out in order. It's O(n + k):

Language
counting_sort.py
def counting_sort(a, k):        # values in 0..k
    count = [0] * (k + 1)
    for x in a:
        count[x] += 1           # tally each value
    out = []
    for v in range(k + 1):
        out.extend([v] * count[v])   # emit in order
    return out

Radix & bucket sort

  • Radix sort sorts numbers (or fixed-length strings) digit by digit, using a stable counting sort on each digit from least to most significant. It's O(d·(n + b)) for d digits in base b.
  • Bucket sort scatters values into buckets by range, sorts each bucket (often with insertion sort), then concatenates. It's O(n) on average for uniformly distributed data.

Watch out

Linear sorts aren't magic — they trade generality for assumptions. Counting/radix need small integer keys; bucket needs a good distribution. Break those assumptions and they lose their edge.

Choosing a sort

SortTimeSpaceNeeds
Heap sortO(n log n)O(1)Comparisons only
CountingO(n + k)O(n + k)Small integer range k
RadixO(d(n + b))O(n + b)Fixed-width keys
BucketO(n) avgO(n)Uniform distribution

Recap & quick check

Key takeaways

  • Heap sort is in-place O(n log n) using a max-heap, but is not stable.
  • Comparison sorts cannot beat Ω(n log n) — there are n! orderings to distinguish.
  • Counting sort tallies values in a known range 0..k for O(n + k) time.
  • Radix sort sorts digit by digit; bucket sort scatters into ranges.
  • Linear sorts need special assumptions about the keys or distribution.

Quick check

1. What is the lower bound for comparison-based sorting?

2. Counting sort runs in O(n + k). What is k?

3. How can counting/radix sort beat the Ω(n log n) bound?

4. A key advantage of heap sort over merge sort is:

Sorting fully arranges data — but often you only need one element, like the median. That's faster. Next up: Module 10 — Selection & Order Statistics.