Heaps and Heapsort
A binary heap is a tree we store flat in an array, with index arithmetic standing in for pointers. We build the max-heap property bottom-up in time, sort in place in by repeatedly extracting the maximum, and reuse the same structure to implement a priority queue.
╌╌╌╌
Quicksort is fast but has a quadratic worst case; mergesort guarantees but needs scratch space. captures the best of both: a worst-case bound and sorting in place, using only a constant amount of extra memory. The trick is a data structure, the binary heap, which doubles as an efficient priority queue and so is worth knowing for its own sake well beyond sorting.
The heap as a tree we never build
A (binary) max-heap is a complete binary tree that obeys one local rule.1
Complete
means the tree is filled level by level, top to bottom and left to
right, with no gaps until possibly the last level. That rigidity is what lets us
discard the tree entirely and store it as a flat array: there is exactly one
shape for a complete tree on nodes, so position in the array is position
in the tree.
We store the heap in an array in level order: the root at , then its two children, then their four children, and so on. With -based indexing the navigation is pure arithmetic, no pointers required:
Doubling an index walks down to a left child; halving walks back up to a parent. On a machine these are single shift operations, which is part of why heaps are fast in practice. Here is a small max-heap shown both as a tree and as the array that actually lives in memory:
Read level by level, the array is
The blue numbers below trace the correspondence: each tree node lives at array slot , so doubling the index (, ) steps down to a child and halving () steps back up to the parent.
Check the index rule on : its children are at and , both smaller, and its parent is at , larger. The largest key always sits at the root, . A heap on nodes is complete, so its height, the number of edges on the longest root-to-leaf path, is . Every operation below costs at most one such trip up or down the tree.
A heap is not a sorted array: it is far weaker, ordering only along ancestor-descendant paths, with no relation between siblings or cousins. That weakness is exactly why a heap is cheap to maintain.
Sift-down: restoring the property at one node
The single primitive every heap operation rests on is . It assumes the subtrees rooted at the two children of are already max-heaps, but itself may be smaller than a child and thus violate the property. It repairs the violation by floating down to its rightful level, a motion usually called sift-down.2
- 1
- 2
- 3if and then
- 4
- 5else
- 6
- 7if and then
- 8
- 9if then
- 10exchange withbigger child moves up
- 11callfix the disturbed subtree
We compare against both children, find the largest of the three, and, if a child wins, swap it up and recurse into the subtree that just received the smaller key. The violation moves strictly down one level each step, so the recursion can run no deeper than the tree is tall: time, extra space (or stack, trivially made iterative). We track the logical length of the heap in a field , which lets the array hold heap and non-heap regions side by side, an arrangement central to heapsort below.
Building a heap bottom-up
To turn an arbitrary array into a heap we call at every internal node, but in the right order. The leaves are already valid one-element heaps, so we start just above them and work upward to the root. Processing a node only after its children are heaps is exactly the precondition demands.
- 1
- 2for downto do
- 3callchildren already heaps
On nodes the leaves are (already heaps, so we skip them), and we sift down the internal nodes in that decreasing order, so that every node is processed only after both of its children are.
The loop invariant makes the correctness immediate.
Initialization: nodes are leaves, hence trivial heaps. Maintenance: the children of are numbered higher than , so by the invariant they head max-heaps, precisely what needs, and it extends the property to . Termination: when , node (and all others) roots a max-heap.
Why it is , not
The easy bound is immediate: there are calls to , each costing , for . That is correct but loose, and the looseness matters. Most nodes are near the bottom of the tree, where has almost nothing to do.
A heap of nodes has at most nodes at height , and a sift-down from height costs . Summing the real work over all heights,
The series converges to , using the standard identity at . The infinite sum is a constant, so the whole bound collapses to .
The intuition behind the algebra: half the nodes are leaves (zero work), a quarter sit one level up (at most one swap each), an eighth two levels up, and so on. Cost per node falls geometrically as the number of nodes at that level rises geometrically, and the two effects cancel to leave a linear total.3 Building a heap is asymptotically negligible compared to the sort that follows.
Heapsort
A max-heap keeps the largest element at the root, . To sort, we repeatedly move that maximum to where it belongs, the back of the array, then shrink the heap and repair it.
- 1call
- 2for downto do
- 3exchange withmax to its final slot
- 4evict from the heap
- 5callrestore the shrunken heap
The array splits into two regions: a heap at the front, , and a growing sorted suffix at the back. Each iteration swaps the heap's maximum into the slot just before the sorted suffix, drops the heap size by one, and runs a single sift-down from the root to re-establish the max-heap property on the smaller heap. After iterations the heap is a single element, necessarily the global minimum, and is sorted ascending.
Cost. is . The loop runs times, each iteration doing work plus one at . The total is
and this is the bound in every case, best, average, and worst, because the heap is always full height during the extractions. Heapsort is in place ( auxiliary space beyond the array) but not stable: the swaps scatter equal keys. Compared to its peers it lacks quicksort's cache-friendliness, since the parent/child jumps roam across memory, which is why quicksort usually wins in practice despite the worse worst case. Heapsort's niche is the guaranteed ceiling with no extra memory, exactly the property introsort borrows as a fallback when quicksort's recursion runs too deep.4
Priority queues
Sorting is only the first use of a heap. The same structure implements a priority queue: a set of elements, each with a key (its priority), supporting
- : add to ;
- : return the element with the largest key;
- : remove and return that element;
- : raise 's key to .
A max-heap answers Maximum in , since it is just , and supports the mutating operations in , the height of the tree.5
- 1
- 2last leaf to the root
- 3
- 4callsift the new root down
- 5return
moves the other direction: bump a key and sift up, repeatedly swapping with the parent while the heap property is violated, again in . Insert is just an from : append the new element as the last leaf, then sift it up.
Reading heapsort through this lens, it is nothing but successive operations, writing each maximum into the vacancy the shrinking heap leaves behind. Priority queues built this way drive Dijkstra's shortest paths, Prim's minimum spanning tree, event-driven simulation, and any scheduler that must repeatedly serve the most urgent task.
Takeaways
- A binary heap is a complete binary tree stored as an array; index arithmetic (, children and ) replaces pointers, and the height is .
- The max-heap property orders only ancestor over descendant: weak enough to maintain cheaply, strong enough to keep the maximum at the root.
- (sift-down) repairs one violation in ; applies it bottom-up in , since work falls geometrically while node counts rise geometrically.
- repeatedly extracts the max into the array's tail, sorting in place in in all cases, though it is unstable and not cache-friendly.
- The same heap is a priority queue: maximum, insert, extract-max, and increase-key, the operations Dijkstra, Prim, and schedulers are built on.
Footnotes
- CLRS, Ch. 6 — Heapsort (§6.1). The binary max-heap as a complete tree stored in an array, with the max-heap property and index arithmetic for parent/children. ↩
- Erickson, Algorithms, Ch. — Data Structures. The sift-down primitive that restores the heap property at one node in . ↩
- CLRS, Ch. 6 — Heapsort (§6.3). runs in , summing geometrically decaying per-level work. ↩
- CLRS, Ch. 6 — Heapsort (§6.4). sorts in place in in all cases by repeatedly extracting the maximum. ↩
- Skiena, The Algorithm Design Manual, §4.3, §12.2 — Heaps and Priority Queues. The heap implements a priority queue with maximum and updates. ↩
╌╌ END ╌╌