Exercises — Heap sort — in-place, O(n log n)
Reminder of the index rules we use throughout (0-based array):

Level 1 — Recognition
L1.1 — Read the tree shape
Problem. The array [9, 7, 8, 3, 6, 2, 1] is stored as a complete binary tree. Using the index rules, list the parent and the two children (if any) of the node at index (value ).
Recall Solution
Node index .
- parent → value .
- left → value .
- right → value .
So node sits under parent , above children and . What it looks like: the middle-of-second-row node in the figure above.
L1.2 — Is it a max-heap?
Problem. Is [9, 7, 8, 3, 6, 2, 1] a valid max-heap? (Every parent both children.)
Recall Solution
Check every parent against its children:
- idx0 (): children idx1 , idx2 → , ✔
- idx1 (): children idx3 , idx4 → , ✔
- idx2 (): children idx5 , idx6 → , ✔
- idx3..6 are leaves (no children) → trivially valid.
Every parent dominates its children, so yes, it is a valid max-heap, and the max () is at index . ✔
L1.3 — Which nodes are leaves?
Problem. For an array of elements, which indices are leaves (no children), and what is the index of the last internal node where build-heap begins?
Recall Solution
Last internal node . Everything with index has no children (since left). So leaves are indices , and build-heap starts sift-down at index and counts down to .
Level 2 — Application
L2.1 — One sift-down
Problem. In the array [2, 8, 5, 1, 4] (), run sift_down(A, 0, 5). Show the array after each swap.
Recall Solution
Start at (val ). left (val ), right (val ).
- Largest of is at idx1 → swap idx0↔idx1:
[8, 2, 5, 1, 4]. Follow down to . - At (val ): left (val ), right (val ). Largest is at idx4 → swap idx1↔idx4:
[8, 4, 5, 1, 2]. Follow down to . - At : left , no children → stop.
Result: [8, 4, 5, 1, 2]. The value "sank" two levels. What it looks like: the small root traded places twice, each time with the larger of the two nodes beneath it.
L2.2 — Full build-heap
Problem. Build a max-heap from [4, 10, 3, 5, 1] (). Show the array after each sift-down.
Recall Solution
Start index . Sift-down , then .
- i=1 (val ): children idx3 , idx4 . already largest → no change.
[4, 10, 3, 5, 1]. - i=0 (val ): children idx1 , idx2 . Largest → swap:
[10, 4, 3, 5, 1]. Follow to (val ): children idx3 , idx4 . Largest → swap:[10, 5, 3, 4, 1]. Follow to : leaf → stop.
Max-heap: [10, 5, 3, 4, 1], root . ✔
L2.3 — Choose the heap type
Problem. You want to sort [7, 2, 9, 4] into descending order using the heap-sort machinery. Which heap type — max or min — and why?
Recall Solution
Heap sort works by repeatedly swapping the root to the current back of the array. Whatever sits at the root gets parked last, then second-last, etc.
- A max-heap parks the biggest first (far end) → produces ascending.
- A min-heap parks the smallest first (far end) → the smallest ends up rightmost → produces descending.
So for descending order, use a min-heap. (See Priority Queue for the same root-access idea in a different context.)
Level 3 — Analysis
L3.1 — Count the swaps in a full sort
Problem. Sort [3, 1, 2] () fully with heap sort. Show both phases and give the sorted output.
Recall Solution
Phase 1 (build). Start idx .
- i=0 (val ): children idx1 , idx2 → largest → no swap. Heap
[3, 1, 2].
Phase 2 (extract), end runs .
- end: swap idx0↔idx2 →
[2, 1, |3]. sift_down(0, size ): child idx1 < → stop.[2, 1, |3]. - end: swap idx0↔idx1 →
[1, |2, 3]. sift_down(0, size ): no children → stop.
Sorted: [1, 2, 3] ascending. ✔
L3.2 — Why does build-heap start halfway?
Problem. Explain precisely why the build loop starts at and not at . What precondition of sift-down would break if we started elsewhere?
Recall Solution
Sift-down's precondition is: both subtrees of node are already valid heaps. We meet this by going bottom-up.
- Indices are leaves — a lone element is already a valid heap, so sifting them is pointless work. Skipping them starts us at the last internal node .
- Going downward to guarantees that when we sift node , all deeper nodes (larger indices) have already been fixed, so their subtrees are valid heaps. Precondition satisfied.
If we started at the root () going up, the root's subtrees would still be raw/unsorted → precondition violated → sift-down could stop early on a bad comparison and leave the heap invalid.
L3.3 — Compare sift-down cost across phases
Problem. In one sift-down, the work is per level of descent. In Phase 1, why is the total build cost and not , even though a single sift-down can be ?
Recall Solution
The height a node can possibly sift is , but most nodes are near the bottom and can barely sift at all. Counting by height from the bottom: at height there are at most nodes, each costing : The series converges to (a constant), so the sum is . Phase 2 is different: there we always sift the root (full height each time) times → . The two phases weight the heights differently, hence different totals.
Level 4 — Synthesis
L4.1 — Design a top- routine
Problem. You have an array of numbers and want the largest in sorted order, with . Adapt heap sort so it does not fully sort. Give the algorithm and its time complexity.
Recall Solution
Full sort wastes work — we only need elements out. Do:
- Build a max-heap over the whole array in .
- Extract only times (swap root to back, shrink, sift-down root). Each extraction is .
The last slots of the array now hold the largest in ascending order (reverse them if you want descending). Cost: When this is essentially linear, far cheaper than the of a full sort. (This is exactly the Priority Queue use-case: repeated max access.)
L4.2 — Repair a nearly-correct heap
Problem. Someone hands you the array [10, 5, 9, 4, 1, 8, 2] and swears it's a max-heap except they overwrote index with the value , giving [3, 5, 9, 4, 1, 8, 2]. Restore the max-heap with minimum work. Which operation, and why is it enough?
Recall Solution
Only the root was corrupted; both subtrees of the root are still valid heaps. That is precisely sift-down's precondition, so a single sift_down(A, 0, 7) suffices — no full rebuild needed.
- i=0 (val ): children idx1 , idx2 → largest → swap:
[9, 5, 3, 4, 1, 8, 2]. Follow to idx2 (val ). - idx2: children idx5 , idx6 → largest → swap:
[9, 5, 8, 4, 1, 3, 2]. Follow to idx5 → leaf → stop.
Restored heap: [9, 5, 8, 4, 1, 3, 2] in , not . ✔
Level 5 — Mastery
L5.1 — Prove the extract phase is , not just
Problem. The parent note bounds Phase 2 by . Show it is also — i.e. there exists an input forcing work — so heap sort's worst case is tight (unlike quicksort's ).
Recall Solution
Consider extracting from a heap of size down to . In each such extraction we swap the root to the back and sift a fresh element down. For the first extractions the heap still has size , so its height is for . There exist heaps (e.g. build one where the sifted element must travel to a leaf every time — take distinct keys arranged so each new root is the global minimum of the remaining heap) where every one of these sift-downs traverses a full root-to-leaf path: Combined with the upper bound , Phase 2 (and hence the whole sort, since build is only ) is . Crucially this holds in the worst case too — heap sort has no bad-pivot blow-up like Quicksort, which is why Introsort switches to heap sort as its safety net.
L5.2 — In-place with space, justified
Problem. Explain why heap sort truly uses extra space, and pinpoint the one implementation choice that guarantees it. Contrast with Merge Sort.
Recall Solution
Heap sort mutates the input array itself: the heap region and the growing sorted tail share one array, separated by the moving boundary end. No second array is allocated for data.
The one choice that guarantees : writing sift-down as an iterative loop (as in the parent's code) instead of recursion. Recursion would push up to stack frames — still small, but no longer strictly . The loop uses a fixed handful of index variables () regardless of .
Contrast: Merge Sort must merge into a separate buffer of size , so it needs extra space. Heap sort's array-as-tree trick (pure index arithmetic on a Complete Binary Tree, no pointers) is what buys the in-place property.
#recall
L1: parent of index (0-based)
L2: after build, root of a max-heap holds
L3: total heap-sort time is build + extract =
L4: top- largest without full sort costs
L5: heap sort worst-case is
Repairing only a corrupted root costs
Connections
- Heap sort — in-place, O(n log n) — parent topic these exercises drill.
- Binary Heap · Complete Binary Tree — the structure every problem manipulates.
- Priority Queue — the top- (L4.1) view of repeated root extraction.
- Quicksort · Introsort — why heap sort's tight (L5.1) matters as a fallback.
- Merge Sort — the -space contrast in L5.2.
- Big-O Notation — dominant-term reasoning used in L3.3.