Chapter 18: Efficient and Specialized Sorting
18.03Beginner / Intermediate5 topics18 min1 interactive animation

Heap sort

Heap sort turns the array into a max-heap and then repeatedly moves the largest element to the end. It sorts in place with a guaranteed O(n log n) worst case, but it is not stable and it is usually slower than quick sort in practice.

Learning goals

  • Describe a binary heap stored in an array and compute parent and child indices
  • Implement siftDown and build a heap in O(n)
  • Trace heap sort's two phases
  • Explain why heap sort is in place but not stable

Prerequisites

Binary treesArray indexingSwapping
18.03.1

Heapify

A max-heap is a complete binary tree where every parent is at least as large as its children. Stored in an array, the children of index i are 2i + 1 and 2i + 2, and the parent of i is (i - 1) / 2. No pointers are needed.

siftDown(i) fixes a single violation: if a child is larger than the node, swap the node with its larger child and continue from there. It assumes both subtrees are already heaps. The animation shows the array both as a tree and as boxes.

Best

O(n log n)

Average

O(n log n)

Worst

O(n log n)

Space

O(1)

Heap sort

Interactive · edit the input

Your input

Try

Heap as a binary tree · 8 nodes

4
0
10
1
3
2
5
3
1
4
8
5
7
6
2
7

4, 10, 3, 5, 1, 8, 7, 2

int[] arr · length 8

4
10
3
5
1
8
7
2
01234567
Being comparedJust swappedFinal position (outside the heap)
Setup

Phase 1 turns the array into a max-heap, where every parent is >= its children. Phase 2 repeatedly moves the maximum to the end.

Step 1 / 102
Java
1public static void heapSort(int[] arr) {
2 int n = arr.length;
3 for (int i = n / 2 - 1; i >= 0; i--) {
4 siftDown(arr, n, i); // build a max-heap
5 }
6 for (int end = n - 1; end > 0; end--) {
7 swap(arr, 0, end); // largest goes to its final slot
8 siftDown(arr, end, 0); // repair the smaller heap
9 }
10}
11
12static void siftDown(int[] arr, int size, int i) {
13 while (true) {
14 int largest = i, l = 2 * i + 1, r = 2 * i + 2;
15 if (l < size && arr[l] > arr[largest]) largest = l;
16 if (r < size && arr[r] > arr[largest]) largest = r;
17 if (largest == i) return;
18 swap(arr, i, largest);
19 i = largest;
20 }
21}

Live state

0 comparisons0 swaps

n

8

No condition evaluated on this step
Execution trace (dry run table)Show 102 steps
StepLineStateWhat happens
1L2n=8Phase 1 turns the array into a max-heap, where every parent is >= its children. Phase 2 repeatedly moves the maximum to the end.
2L3i=3 n=8Heapify the subtree rooted at 3. Indices 4..7 are leaves and are already heaps.
3L14i=3 size=8 l=7 r=8Sift down from index 3 (5). Its children are at 7 and 8; 8 is outside the heap.
4L15i=3 l=7 largest=3The left child is not larger.
5L17i=3 largest=35 is at least as large as both children, so the heap property holds here.
6L3i=2 n=8Heapify the subtree rooted at 2. Indices 4..7 are leaves and are already heaps.
7L14i=2 size=8 l=5 r=6Sift down from index 2 (3). Its children are at 5 and 6.
8L15i=2 l=5 largest=2The left child is larger.
9L16i=2 r=6 largest=5The right child is not larger.
10L17i=2 largest=5A child is larger than 3, so swap with the larger child.
11L18i=2 largest=5Swapped arr[2] and arr[5]. Continue sifting from 5.
12L14i=5 size=8 l=11 r=12Sift down from index 5 (3). Its children are at 11 and 12, both outside the heap.
13L17i=5 largest=53 is at least as large as both children, so the heap property holds here.
14L3i=1 n=8Heapify the subtree rooted at 1. Indices 4..7 are leaves and are already heaps.
15L14i=1 size=8 l=3 r=4Sift down from index 1 (10). Its children are at 3 and 4.
16L15i=1 l=3 largest=1The left child is not larger.
17L16i=1 r=4 largest=1The right child is not larger.
18L17i=1 largest=110 is at least as large as both children, so the heap property holds here.
19L3i=0 n=8Heapify the subtree rooted at 0. Indices 4..7 are leaves and are already heaps.
20L14i=0 size=8 l=1 r=2Sift down from index 0 (4). Its children are at 1 and 2.
21L15i=0 l=1 largest=0The left child is larger.
22L16i=0 r=2 largest=1The right child is not larger.
23L17i=0 largest=1A child is larger than 4, so swap with the larger child.
24L18i=0 largest=1Swapped arr[0] and arr[1]. Continue sifting from 1.
25L14i=1 size=8 l=3 r=4Sift down from index 1 (4). Its children are at 3 and 4.
26L15i=1 l=3 largest=1The left child is larger.
27L16i=1 r=4 largest=3The right child is not larger.
28L17i=1 largest=3A child is larger than 4, so swap with the larger child.
29L18i=1 largest=3Swapped arr[1] and arr[3]. Continue sifting from 3.
30L14i=3 size=8 l=7 r=8Sift down from index 3 (4). Its children are at 7 and 8; 8 is outside the heap.
31L15i=3 l=7 largest=3The left child is not larger.
32L17i=3 largest=34 is at least as large as both children, so the heap property holds here.
33L5n=8Max-heap built: the largest value 10 is at the root.
34L7end=7Swap the root with arr[7]. 10 is now in its final position and the heap shrinks to 7 elements.
35L14i=0 size=7 l=1 r=2Sift down from index 0 (2). Its children are at 1 and 2.
36L15i=0 l=1 largest=0The left child is larger.
37L16i=0 r=2 largest=1The right child is larger still.
38L17i=0 largest=2A child is larger than 2, so swap with the larger child.
39L18i=0 largest=2Swapped arr[0] and arr[2]. Continue sifting from 2.
40L14i=2 size=7 l=5 r=6Sift down from index 2 (2). Its children are at 5 and 6.
41L15i=2 l=5 largest=2The left child is larger.
42L16i=2 r=6 largest=5The right child is larger still.
43L17i=2 largest=6A child is larger than 2, so swap with the larger child.
44L18i=2 largest=6Swapped arr[2] and arr[6]. Continue sifting from 6.
45L14i=6 size=7 l=13 r=14Sift down from index 6 (2). Its children are at 13 and 14, both outside the heap.
46L17i=6 largest=62 is at least as large as both children, so the heap property holds here.
47L7end=6Swap the root with arr[6]. 8 is now in its final position and the heap shrinks to 6 elements.
48L14i=0 size=6 l=1 r=2Sift down from index 0 (2). Its children are at 1 and 2.
49L15i=0 l=1 largest=0The left child is larger.
50L16i=0 r=2 largest=1The right child is larger still.
51L17i=0 largest=2A child is larger than 2, so swap with the larger child.
52L18i=0 largest=2Swapped arr[0] and arr[2]. Continue sifting from 2.
53L14i=2 size=6 l=5 r=6Sift down from index 2 (2). Its children are at 5 and 6; 6 is outside the heap.
54L15i=2 l=5 largest=2The left child is larger.
55L17i=2 largest=5A child is larger than 2, so swap with the larger child.
56L18i=2 largest=5Swapped arr[2] and arr[5]. Continue sifting from 5.
57L14i=5 size=6 l=11 r=12Sift down from index 5 (2). Its children are at 11 and 12, both outside the heap.
58L17i=5 largest=52 is at least as large as both children, so the heap property holds here.
59L7end=5Swap the root with arr[5]. 7 is now in its final position and the heap shrinks to 5 elements.
60L14i=0 size=5 l=1 r=2Sift down from index 0 (2). Its children are at 1 and 2.
61L15i=0 l=1 largest=0The left child is larger.
62L16i=0 r=2 largest=1The right child is not larger.
63L17i=0 largest=1A child is larger than 2, so swap with the larger child.
64L18i=0 largest=1Swapped arr[0] and arr[1]. Continue sifting from 1.
65L14i=1 size=5 l=3 r=4Sift down from index 1 (2). Its children are at 3 and 4.
66L15i=1 l=3 largest=1The left child is larger.
67L16i=1 r=4 largest=3The right child is not larger.
68L17i=1 largest=3A child is larger than 2, so swap with the larger child.
69L18i=1 largest=3Swapped arr[1] and arr[3]. Continue sifting from 3.
70L14i=3 size=5 l=7 r=8Sift down from index 3 (2). Its children are at 7 and 8, both outside the heap.
71L17i=3 largest=32 is at least as large as both children, so the heap property holds here.
72L7end=4Swap the root with arr[4]. 5 is now in its final position and the heap shrinks to 4 elements.
73L14i=0 size=4 l=1 r=2Sift down from index 0 (1). Its children are at 1 and 2.
74L15i=0 l=1 largest=0The left child is larger.
75L16i=0 r=2 largest=1The right child is not larger.
76L17i=0 largest=1A child is larger than 1, so swap with the larger child.
77L18i=0 largest=1Swapped arr[0] and arr[1]. Continue sifting from 1.
78L14i=1 size=4 l=3 r=4Sift down from index 1 (1). Its children are at 3 and 4; 4 is outside the heap.
79L15i=1 l=3 largest=1The left child is larger.
80L17i=1 largest=3A child is larger than 1, so swap with the larger child.
81L18i=1 largest=3Swapped arr[1] and arr[3]. Continue sifting from 3.
82L14i=3 size=4 l=7 r=8Sift down from index 3 (1). Its children are at 7 and 8, both outside the heap.
83L17i=3 largest=31 is at least as large as both children, so the heap property holds here.
84L7end=3Swap the root with arr[3]. 4 is now in its final position and the heap shrinks to 3 elements.
85L14i=0 size=3 l=1 r=2Sift down from index 0 (1). Its children are at 1 and 2.
86L15i=0 l=1 largest=0The left child is larger.
87L16i=0 r=2 largest=1The right child is larger still.
88L17i=0 largest=2A child is larger than 1, so swap with the larger child.
89L18i=0 largest=2Swapped arr[0] and arr[2]. Continue sifting from 2.
90L14i=2 size=3 l=5 r=6Sift down from index 2 (1). Its children are at 5 and 6, both outside the heap.
91L17i=2 largest=21 is at least as large as both children, so the heap property holds here.
92L7end=2Swap the root with arr[2]. 3 is now in its final position and the heap shrinks to 2 elements.
93L14i=0 size=2 l=1 r=2Sift down from index 0 (1). Its children are at 1 and 2; 2 is outside the heap.
94L15i=0 l=1 largest=0The left child is larger.
95L17i=0 largest=1A child is larger than 1, so swap with the larger child.
96L18i=0 largest=1Swapped arr[0] and arr[1]. Continue sifting from 1.
97L14i=1 size=2 l=3 r=4Sift down from index 1 (1). Its children are at 3 and 4, both outside the heap.
98L17i=1 largest=11 is at least as large as both children, so the heap property holds here.
99L7end=1Swap the root with arr[1]. 2 is now in its final position and the heap shrinks to 1 element.
100L14i=0 size=1 l=1 r=2Sift down from index 0 (1). Its children are at 1 and 2, both outside the heap.
101L17i=0 largest=01 is at least as large as both children, so the heap property holds here.
102L10Sorted in place with 27 comparisons and 20 swaps. Equal keys can swap past each other, so heap sort is not stable.
18.03.2

Build heap

To build a heap from an arbitrary array, call siftDown on every non-leaf node from the last parent n/2 - 1 down to 0. The leaves are already one-element heaps.

This costs O(n), not O(n log n). A node at height h sifts down at most h levels, and there are about n / 2^(h+1) nodes at height h. The sum of h · n / 2^(h+1) over all h is O(n), because the series Σ h / 2^h converges to 2.

Calling add n times (sift up) also builds a heap, but costs O(n log n).

18.03.3

Repeated extract maximum

Phase 2: the maximum is at index 0. Swap it with the last element of the heap, shrink the heap size by one so that element is now outside it and finished, then siftDown(0) to restore the heap property in the smaller heap.

Repeat until the heap has one element. Each extraction costs O(log n), so phase 2 costs O(n log n). The tail of the array fills with the largest values in increasing order, which is why the final array is ascending.

PhaseWorkCost
Build max-heapsiftDown for n/2 nodesO(n)
Extract n - 1 timesswap + siftDown(0)O(n log n)
TotalO(n log n)
18.03.4

In-place property

Heap sort needs only a few variables, so it uses O(1) extra memory. Merge sort needs O(n) and quick sort needs O(log n) stack. Together with the worst-case guarantee that is what makes heap sort attractive for memory-constrained systems and for safety-critical code that cannot tolerate quick sort's quadratic worst case.

Quick sort usually beats it in practice. Heap sort jumps between index i and 2i + 1, which is poor for CPU caches, and it does more comparisons: about 2n log₂ n against 1.39 n log₂ n.

18.03.5

Instability

Heap sort is not stable. Swapping the root with the last element moves an element across the entire array, and sifting moves it between levels, so equal keys can pass each other.

Example: for [2a, 2b] the build phase does nothing. Extraction swaps the root 2a with 2b, giving [2b, 2a]: order reversed. Run the “Duplicates” preset and compare the a/b tags.

Practice ladder

Now try it yourself

Easy

  • Given an index, compute its parent and children
  • Is this array a valid max-heap?

Medium

  • Implement siftDown and heapify
  • Sort an array in descending order with a min-heap variant
  • Kth largest element with a size-k min-heap

Advanced

  • Prove that building a heap is O(n)
  • Bottom-up heap sort that sifts the hole to a leaf first and saves comparisons
  • Smoothsort (adaptive heap sort) overview

Key takeaways

  • Heap sort is O(n log n) in every case, in place, and not stable.
  • Build the heap in O(n) with siftDown from the last parent; then extract the maximum n - 1 times.
  • In practice it loses to quick sort on cache behaviour, but it has no bad inputs.