← SciSim / Computer Science

🌳 Heapsort

Heap Data Structure | Build-Heap + Extract-Max

Standard Undergraduate

§1 — Interactive Simulation

Array View
Tree View
Root/Max (being extracted) Sifting down Sorted region Heap region
Speed:
0
Comparisons
0
Swaps
0
Step
10
n
BUILD-MAX-HEAP(A):
  for i ← ⌊n/2⌋-1 downto 0: SIFT-DOWN(A,i,n)
HEAPSORT(A):
  BUILD-MAX-HEAP(A)
  for i ← n-1 downto 1:
    swap(A[0], A[i])
    SIFT-DOWN(A, 0, i)
SIFT-DOWN(A, i, size):
  largest ← i; l←2i+1; r←2i+2
  if l<size and A[l]>A[largest]: largest←l
  if r<size and A[r]>A[largest]: largest←r
  if largest≠i: swap; SIFT-DOWN(A,largest,size)
Index Labels
Value Labels
Phase Label

§2 — The Idea, Step by Step

Picture a class lining up tallest-first. The lazy way is to scan everyone, pull out the tallest, then scan the rest and pull out the next tallest, again and again. Heapsort does the same "keep grabbing the biggest" trick — but it organizes the crowd first so finding the biggest each time is almost free.

Build a champion-on-top pile. Heapsort treats the array as a family tree: the element at index $i$ is the parent of the elements at $2i+1$ and $2i+2$. A max-heap just means every parent beats both its children, so the overall champion ends up at the very top, index $0$. The first phase, BUILD-MAX-HEAP, shuffles people into that shape from the bottom up.
Pull the top, repair, repeat. Once the champion sits at index $0$, swap it to the back of the array (its final sorted home) and shrink the heap by one. The new index-$0$ element is probably too small, so it "sifts down," trading places with its larger child until parents beat children again. Do this $n-1$ times and the array comes out sorted. The dashed heap boundary line in the sim marks where the finished, sorted tail begins.
Count the work. With $n=16$, building the heap takes only about $n$ cheap fixes, and each of the $\sim16$ extractions sifts down through at most $\log_2 16 = 4$ levels — roughly $16\times4 = 64$ settle-steps. Compare that to a scan-and-pick sort's $16^2 = 256$ comparisons. The gap widens fast as $n$ grows: heapsort runs in $O(n\log n)$.
The precise picture. BUILD-MAX-HEAP is actually $O(n)$, not $O(n\log n)$, because most nodes sit near the leaves where sifting is short — the cost sum $\sum_{h\ge 0} h/2^h$ converges to $2$. The extraction phase, $\sum_{i=1}^{n-1}\log i = O(n\log n)$, dominates. That bound holds in the best, average, and worst case, all using only $O(1)$ extra space — heapsort's signature guarantee. The Array Size slider sets $n$; the Preset menu feeds it sorted, reversed, or nearly-sorted inputs.
Try this in the sim above. (1) Set a large $n$ and watch the BUILD phase finish with only a handful of swaps before the heap boundary appears — that is the $O(n)$ build. (2) Open Tree View and step through one SIFT-DOWN: the highlighted node falls at most $\lfloor\log_2 n\rfloor$ levels. (3) Switch the Preset between "Reverse Sorted" and "Random" — the comparison and swap counts barely budge, showing why heapsort's worst case is no worse than its best.

§3 — Complexity Analysis & Derivation

SymbolMeaning
nNumber of elements
hHeight of heap = ⌊log₂n⌋
T_buildCost of BUILD-MAX-HEAP
T_extractCost of n SIFT-DOWN operations
Step 1 — SIFT-DOWN cost. One SIFT-DOWN from height h in a heap of size n costs at most 2h comparisons (compare with left child, compare with right child at each level). Height of root = ⌊log₂n⌋.
Step 2 — BUILD-MAX-HEAP analysis. There are at most ⌈n/2^(h+1)⌉ nodes at height h. Total cost: \[T_{build} \le \sum_{h=0}^{\lfloor\log n\rfloor} \left\lceil\frac{n}{2^{h+1}}\right\rceil \cdot 2h = n\sum_{h=0}^{\infty}\frac{h}{2^h} = n \cdot 2 = O(n)\] Therefore BUILD-MAX-HEAP is O(n), NOT O(n log n)!
Step 3 — HEAPSORT extraction phase. Extract-max n−1 times. Each extraction: O(1) swap + SIFT-DOWN on heap of size i (i from n−1 down to 1). SIFT-DOWN costs O(log i) ≤ O(log n). Total: Σᵢ₌₁ⁿ⁻¹ O(log i) = O(log(n!)) = O(n log n) by Stirling's approximation.
Step 4 — Overall complexity. T(n) = T_build + T_extract = O(n) + O(n log n) = O(n log n). This holds for ALL cases (best, average, worst). Heapsort is the only comparison sort with guaranteed O(n log n) AND O(1) space.
Step 5 — Space complexity. In-place algorithm: O(1) extra space. The heap is maintained within the input array itself using the implicit binary tree representation: parent(i)=⌊(i−1)/2⌋, left(i)=2i+1, right(i)=2i+2.
Step 6 — Stability. NOT stable. During SIFT-DOWN, non-adjacent equal elements can be reordered. Cannot be made stable without O(n) extra space.
Step 7 — Practical performance. Despite optimal asymptotic bounds, Heapsort is ~2–5× slower than Quicksort in practice due to poor cache behavior: SIFT-DOWN accesses A[2i+1] and A[2i+2] which are far apart in memory for large arrays. Memory access pattern is nearly random at large scales.

§4 — Frequently Asked Questions

Q1. Why does BUILD-MAX-HEAP start from ⌊n/2⌋−1 instead of n−1?concept
Leaf nodes (indices ⌊n/2⌋ to n−1) are already valid single-element max-heaps — no sifting needed. Starting from ⌊n/2⌋−1 (the last internal node) ensures every non-leaf is processed bottom-up. Starting from n−1 would waste O(n/2) trivial SIFT-DOWN calls on leaves.
Q2. Why is BUILD-MAX-HEAP O(n) and not O(n log n)?complex
Intuition: most nodes are near the bottom. Half the nodes are leaves (height 0, cost 0). A quarter are at height 1 (cost ≤2). An eighth at height 2 (cost ≤4). The sum converges: n·Σ(h/2^h) = 2n. The harmonic-series-like convergence makes the total O(n).
Q3. When would you prefer Heapsort over Quicksort?applied
Use Heapsort when: (1) worst-case O(n log n) is required (real-time systems), (2) memory is extremely constrained (embedded systems with O(1) space), (3) input is adversarially chosen to trigger Quicksort's worst case. Introsort (used in C++ STL) falls back to Heapsort for this reason.
Q4. What does the tree view show that the array view doesn't?sim
The tree view shows the implicit binary tree structure. Each bar in the array corresponds to a node. The parent-child relationships (parent at i, children at 2i+1 and 2i+2) become visually obvious. You can see SIFT-DOWN as a node "falling" down the tree to restore the heap property.
Q5. Can a min-heap be used to sort in descending order?nonobv
Yes. Building a min-heap and repeatedly extracting the minimum gives ascending order… wait, that's the same as ascending with max-heap. Key insight: max-heap + Heapsort gives ascending order (smallest ends up first after all extractions are placed at the end). Min-heap + Heapsort gives descending order. This seems counterintuitive but follows from where extracted elements are placed.
Q6. How is Heapsort related to the Priority Queue ADT?deep
Heapsort is essentially: build a priority queue from all elements (BUILD-MAX-HEAP), then repeatedly call EXTRACT-MAX and place results in sorted order. Any data structure supporting O(log n) insert and O(log n) extract-max could be used for sorting in O(n log n), but the binary heap's implicit array representation makes Heapsort in-place, which linked-structure PQs cannot achieve.

§5 — Common Misconceptions

Conceptual Misconceptions

❌ "BUILD-MAX-HEAP is O(n log n) because we call SIFT-DOWN n/2 times."

✅ Most calls are on short subtrees near the leaves. The total is bounded by the converging series Σ h/2^h = 2, giving O(n).

❌ "A max-heap is a sorted array."

✅ Max-heap only guarantees parent ≥ children. Siblings have no ordering. A[0] is maximum, but A[1] and A[2] are not necessarily in sorted order with each other.

❌ "Heapsort is always faster than Mergesort since it uses O(1) space."

✅ Heapsort is typically slower in practice due to cache misses. Mergesort with O(n) space often runs faster on modern hardware despite the extra memory.

Implementation Misconceptions

❌ Building the heap by inserting elements one by one (O(n log n)) instead of using BUILD-MAX-HEAP (O(n)).

✅ Top-down insertion is valid but suboptimal. Bottom-up BUILD-MAX-HEAP exploits the fact that most sift-down operations are cheap near the leaves.

❌ Using 1-indexed arrays without adjusting child/parent formulas.

✅ 0-indexed: parent=(i-1)/2, left=2i+1, right=2i+2. 1-indexed: parent=i/2, left=2i, right=2i+1. Mixing these formulas is a common bug.

❌ Forgetting to decrease heap size after each extraction (treating the whole array as heap).

✅ After swapping A[0] with A[end], the heap size decreases by 1. SIFT-DOWN must be called with the new (reduced) heap size, not n.