Definition · HeapsortHeapsort sorts a[1..n] in two phases. Build: rearrange a into a max-heap in place, bottom up. Teardown: for m = n down to 2, swap a[1] with a[m], treat the heap as having m − 1 elements, and sift the new a[1] down until the heap property holds again.
The max-heap is the choice that makes the sort ascending. The root is the largest remaining key, and the slot it is swapped into is the last slot the heap still owns, so the largest key lands where the sorted array wants it and is immediately outside the heap.
- Build the max-heap, bottom up, from the last internal node to the root.
- Swap a[1] with a[m]. The key now at a[m] is final.
- Shrink the heap to m − 1 slots, so a[m] is no longer part of it.
- Sift a[1] down: compare it with its children, swap with the larger if the larger is bigger, and repeat.
- Repeat from step two until m = 1.
Example · Eight keys, countedOn 8 1 3 6 5 2 7 4 the build costs 8 comparisons and produces the heap 8 6 7 4 5 2 3 1. The seven rounds of teardown cost 18 more, so heapsort spends 26 comparisons in all and performs 20 swaps. Merge sort on the same eight keys spends 17, and ⌈log₂ 8!⌉ = 16 comparisons are necessary for any comparison sort on any array of eight. Every one of those three numbers was produced by running something.
The build is the cheap half and it is the half readers expect to dominate. At 16 keys the lab measures 22 comparisons for the build and 58 for the teardown, out of 80. The asymmetry is structural: the build's cost is dominated by the many nodes near the bottom, which sift down almost no distance, while every round of the teardown drops a key from the root and usually carries it most of the way back down.
The two promises heapsort does not make
It is not adaptive. The comparison counts at 16 keys are 85 for increasing input, 80 for a shuffle, 72 for decreasing input: not only is sorted input no cheaper, it is the most expensive of the three, because an ascending array is the worst possible starting shape for a max-heap build and every key has to travel. A reader who expects “nearly sorted input is cheaper” is expecting a property insertion sort has and this algorithm does not.
It is not stable. The lab's tie test builds eight records with four distinct keys, 1#1 2#2 3#3 4#4 1#5 2#6 3#7 4#8, and heapsort returns 1#5 1#1 2#6 2#2 3#3 3#7 4#8 4#4. Three pairs of equal keys come out reversed. One would have been enough.
What it does promise is worth being clear about, because it is the reason the algorithm is used. Θ(1) extra memory, and a Θ(n log n) worst case with no bad input and no randomness — which is exactly what quicksort cannot offer and merge sort can only offer at the cost of a second array. When memory is the binding constraint and a guarantee is wanted, this is the sort.
The cost of those two promises is a constant factor, and the lab prints it as a ratio. At 16 keys heapsort spends 80 comparisons against merge sort's 48 on the same array: both are Θ(n log n) and heapsort's constant is larger, chiefly because a sift-down compares the key with both children at every level. That same factor of about two reappears in “k-Way Merging and Sorting in Practice”, where a heap merges k runs and pays for it in exactly the same way.
Two figures on this page are measurements of one array each — 26 at eight keys, 80 at sixteen — and one is a proof: ⌈log₂ n!⌉, which no comparison sort can go under on any array. At 16 keys that is 45, and merge sort's 48 and heapsort's 80 both sit above it. The gap between 45 and 80 is not slack in the bound; it is the price of the two promises.