3.4.13Trees

Heap sort — in-place, O(n log n)

2,014 words9 min readdifficulty · medium1 backlinks

WHAT is a heap (as an array)?

WHY array, not pointers? A complete tree has no holes — every level is full except possibly the last, filled left-to-right. That regular shape means we can compute child/parent positions with arithmetic, so we need zero pointers and zero extra memory. This is the secret to "in-place".

Recall Why is

parent = (i-1)/2? Left child of pp is 2p+12p+1, right is 2p+22p+2. Invert both: from 2p+12p+1 we get p=(i1)/2p=(i-1)/2; from 2p+22p+2 we get p=(i2)/2p=(i-2)/2. Integer floor division of (i1)/2(i-1)/2 collapses both cases into one formula.


HOW the algorithm works (two phases)

Phase 1 — Build the heap (heapify the whole array)

Turn an unordered array into a max-heap.

Phase 2 — Sort by repeated extraction

Swap root (max) with last heap slot, shrink heap by one, sift the new root down. Repeat.

The workhorse for both is sift-down (a.k.a. max_heapify).

Figure — Heap sort — in-place, O(n log n)
def sift_down(A, i, n):          # heap occupies A[0..n-1]
    while True:
        l, r = 2*i + 1, 2*i + 2
        largest = i
        if l < n and A[l] > A[largest]: largest = l
        if r < n and A[r] > A[largest]: largest = r
        if largest == i: break        # heap property restored
        A[i], A[largest] = A[largest], A[i]
        i = largest                   # follow the swap downward
 
def heap_sort(A):
    n = len(A)
    # Phase 1: build max-heap, bottom-up from last parent
    for i in range(n//2 - 1, -1, -1):
        sift_down(A, i, n)
    # Phase 2: extract max repeatedly
    for end in range(n - 1, 0, -1):
        A[0], A[end] = A[end], A[0]   # biggest -> sorted tail
        sift_down(A, 0, end)          # restore heap of size `end`

Derivation of the time complexity

Phase 2 is O(nlogn)O(n \log n) — easy

We do n1n-1 extractions, each a sift-down on a heap of size n\le n: k=1n1O(logk)=O(nlogn).\sum_{k=1}^{n-1} O(\log k) = O(n \log n).

Phase 1 (build) is O(n)O(n) — surprising!

Naive guess: nn nodes ×logn\times \log n = O(nlogn)O(n\log n). Tighter truth: most nodes are near the bottom and sift-down only a little.

At height hh from the bottom there are at most n/2h+1\lceil n / 2^{h+1}\rceil nodes, each costing O(h)O(h): Tbuild=h=0lognn2h+1O(h)=O ⁣(nh=0h2h).T_{\text{build}} = \sum_{h=0}^{\lfloor\log n\rfloor} \frac{n}{2^{h+1}}\,O(h) = O\!\left(n\sum_{h=0}^{\infty}\frac{h}{2^h}\right). Now evaluate the series. Let S=h0hxh=x(1x)2S=\sum_{h\ge0} h x^h = \dfrac{x}{(1-x)^2}. At x=12x=\tfrac12: S=1/2(1/2)2=2S = \dfrac{1/2}{(1/2)^2} = 2. Hence Tbuild=O(2n)=O(n).T_{\text{build}} = O(2n) = O(n).


Worked examples


Common mistakes (steel-manned)


Flashcards

Heap sort time complexity (best, average, worst)
O(nlogn)O(n\log n) in all three cases.
Heap sort extra space
O(1)O(1) — fully in-place (iterative sift-down).
Is heap sort stable?
No — sifting swaps reorder equal keys.
Index of left child of node ii (0-based)
2i+12i+1.
Index of parent of node ii (0-based)
(i1)/2\lfloor (i-1)/2 \rfloor.
Where does build-heap loop start?
At the last internal node, index n/21\lfloor n/2\rfloor - 1, going down to 0.
Why is build-heap O(n)O(n) not O(nlogn)O(n\log n)?
Most nodes are near the bottom; h(n/2h+1)h=O(n)\sum_{h} (n/2^{h+1})\cdot h = O(n) since h/2h=2\sum h/2^h = 2.
What precondition does sift-down need?
Both subtrees of node ii are already valid heaps.
Phase 2 step in one line
Swap root with last heap slot, shrink heap, sift-down new root.
Max-heap vs min-heap for ascending sort?
Use a max-heap; the largest gets parked at the end each round.
Heap stored in array — why no pointers?
It is a complete tree, so child/parent positions are pure index arithmetic.
Recall Feynman: explain to a 12-year-old

Imagine a pile of numbered cards arranged like a pyramid, where the rule is every card is bigger than the two cards below it. So the biggest card is always on top. To sort them: take the top (biggest) card and put it at the very end of your row. Then a small card is now on top breaking the rule, so you let it "sink" down — keep swapping it with the bigger of the two cards beneath until the pyramid rule is fixed again. Now the new top is the next-biggest; park it just before the last one. Repeat until the pyramid is empty — your row is sorted, and you never needed a second table to do it on!

Connections

  • Binary Heap — the data structure heap sort runs on.
  • Priority Queue — same heap, used for repeated min/max access.
  • Quicksort — also in-place but O(n2)O(n^2) worst case; heap sort guarantees O(nlogn)O(n\log n).
  • Merge SortO(nlogn)O(n\log n) and stable, but needs O(n)O(n) extra space.
  • Complete Binary Tree — why index arithmetic works.
  • Big-O Notation — for the build-heap series analysis.
  • Introsort — hybrid that falls back to heap sort to dodge quicksort's worst case.

Concept Map

stored as

index arithmetic

enables

with heap property

restored by

used in

used in

bottom-up from n/2-1

swap root to tail

repeat n times

combined with

Complete binary tree

Flat array

parent and child positions

In-place, zero pointers

Max-heap: root is max

Sift-down / max_heapify

Phase 1: Build heap

Phase 2: Extract max

Sorted region grows right

O of n log n total

Hinglish (regional understanding)

Intuition Hinglish mein samjho

Heap sort ka core idea simple hai: ek max-heap banao, jisme rule hota hai ki har parent apne dono children se bada ya barabar hota hai — isliye sabse bada element hamesha root (index 0) par hota hai. Heap ko hum ek normal array me hi store karte hain, koi pointer nahi chahiye, kyunki tree complete hai aur child/parent ki position sirf index arithmetic se nikal jaati hai (2i+12i+1, 2i+22i+2, (i1)/2\lfloor(i-1)/2\rfloor). Isi wajah se heap sort in-place hai — extra memory O(1)O(1).

Algorithm do phase me chalta hai. Phase 1 (build heap): array ke last internal node (n/21n/2-1) se neeche tak sift_down chalao, bottom-up, taaki poora array max-heap ban jaye. Phase 2 (extract): root (sabse bada) ko array ke last slot se swap karo, heap ka size ek kam kar do, aur naye root ko sift_down karke heap property wapas theek karo. Har baar sabse bada element peeche "park" hota jaata hai, aur sorted region right side se badhta jaata hai.

Complexity ka maza yahan hai: ek sift-down O(logn)O(\log n) leta hai, nn extractions = O(nlogn)O(n\log n). Lekin build-heap sirf O(n)O(n) hai, O(nlogn)O(n\log n) nahi! Kyunki zyada-tar nodes leaves ke paas hote hain aur thoda hi neeche jaate hain — series h/2h=2\sum h/2^h = 2 converge hoti hai. Total fir bhi O(nlogn)O(n\log n), aur worst case bhi O(nlogn)O(n\log n) (quicksort ki tarah O(n2)O(n^2) nahi). Bas yaad rakho: heap sort stable nahi hai, aur ascending sort ke liye max-heap use karna — min-heap se descending milega. Common galti: Phase 2 me sift_down ko full length pass mat karo, shrinking size end do, warna sorted tail kharab ho jaayega.

Go deeper — visual, from zero

Test yourself — Trees

Connections