3.6.5 · D2Sorting & Searching

Visual walkthrough — Heap sort — O(n log n), in-place, not stable

1,920 words9 min readBack to topic

We will sort this array the whole way through:

Seven numbers. Positions numbered from left. That position number is called the index — think of it as the seat number of each value.


Step 1 — Draw the array as a tree

WHAT. We are only re-drawing the same seven numbers — nothing moves yet. We just stack them in a triangle.

WHY. A flat array has no obvious "parent" or "child". But a tree does, and — this is the magic — because there are no gaps, the seat numbers obey a clean formula. For the value at seat :

  • : "go down one level, take the left branch." At that is seat .
  • : same but the right branch — seat .
  • : the reverse trip, back up to the parent. The (floor) just chops off any fraction, because a seat number must be a whole number.

PICTURE. Follow the coloured arrows: seat (value ) points down to seats and .

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

Step 2 — State the goal: the heap property

WHAT. We check our tree against this rule and find it broken everywhere — seat holds but its child is bigger.

WHY this exact rule. We want the maximum to be trivially findable (just read seat ). The heap property is the weakest rule that guarantees that, while still being cheap to repair. We do not demand a fully sorted tree — that would be expensive. Just "parent beats children," locally.

PICTURE. Red edges mark every parent smaller than a child — every place the rule is violated right now.

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

Step 3 — The one repair tool: sift-down

WHAT. We fix a single out-of-place value by letting it sink to its rightful level.

WHY the larger child, not just any child? If we swapped the parent with the smaller child, the smaller child would rise above the larger one — instantly breaking the rule between the two children. Swapping with the larger child promotes the true local champion, so the top is fixed and only the subtree below might still need work.

WHY does it terminate? Each swap moves the value strictly down one level. A tree of nodes is only about levels tall, so at most swaps.

PICTURE. Watch value at seat : its children are and . Larger child is , so and swap; lands at a leaf, done.

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

Step 4 — Build the heap from the bottom up

WHAT. We repair small subtrees first, then bigger ones, so that whenever we sift a node, its children's subtrees are already proper heaps — exactly the precondition Step 3 required.

WHY bottom-up and not top-down? If you fixed the root first, its children could still be a mess, so you would have to keep re-fixing. Building upward means every fix is final.

The trace (heap size throughout):

Call Node Action Array after
sift(2) swap with child
sift(1) already — no swap
sift(0) swap with ; then vs → swap with

PICTURE. The finished max-heap: has floated to the top, every red edge from Step 2 is now green.

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

Step 5 — Crown the champion, exile it to the back

WHAT. Swap (the ) with (the ). Now is in its final resting place. Reduce the live heap size from to .

WHY it works. Seat is guaranteed to hold the current maximum (Step 2). Placing maxima at the back, one at a time, from largest inward, is literally selection sort — but selection made cheap by the heap.

The damage. After the swap, the small value now sits at the root and violates the heap property. But its two subtrees are still perfect heaps → precondition for sift-down holds. One sift-down(0, size=6) repairs it.

PICTURE. Blue = the frozen sorted zone growing from the right; the heap shrinks on its left.

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

Step 6 — Repeat until the heap is empty

WHAT. Do Step 5 over and over: swap root to the last live seat, shrink, sift-down the root. Each pass freezes one more value into the sorted zone.

WHY times and not ? When only one live element remains, it is trivially the smallest and already in place — no work needed. So the loop runs from end = n-1 down to 1.

The full drain (each row: state after crowning + re-heapifying):

Pass Crown to back Heap size now Array (│ = sorted wall)
1 6
2 5
3 4
4 3
5 2
6 1

Result: — sorted, and we never used a second array.

PICTURE. The blue sorted wall marching leftward, one crown per pass.

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

Step 7 — Edge and degenerate cases

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

The one-picture summary

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

The array becomes a tree (Step 1), the tree becomes a heap by bottom-up sifting (Steps 3–4), then each pass crowns the top value into the growing sorted zone and re-heapifies (Steps 5–6) — all inside the one original array.

Recall Feynman retelling — say it like a story

Imagine seven kids in a bracket. First I arrange them into a triangle where every coach beats both players under them — I do this from the bottom coaches upward so each fix sticks (that's build heap). Now the overall champion is standing at the very top. I shake their hand and walk them to the last empty chair on the right — that chair is now theirs forever. I pull a random kid up to the top to replace them, and they sink down until the "coach beats players" rule holds again. Champion, chair, sink. Champion, chair, sink. Each time the right-hand row of taken chairs grows leftward, and the tournament shrinks. When one kid is left, they're already the smallest and sit down on their own. The chairs, left to right, now read smallest to largest — and I never needed a second room. Because I re-run a full mini-tournament after every crowning, it always costs about handshakes, sorted-input or not — and because the champion sometimes leaps over an equal-ranked kid, ties can swap order, which is why heap sort is not stable.

Recall Quick self-test

Left child of seat ::: seat Where does the max sit in a max-heap ::: seat (the root) First node we sift when building a heap of size ::: seat Time complexity in the best case ::: still Is heap sort stable ::: no — crown-swaps move equal keys apart