G27
Self-checkedTier 1 11 min readalgo.sorting-heap

Heap sort

Heap sort as an algorithm: build a max-heap bottom-up in linear time, then repeatedly swap the maximum to the end and sift down. Why building costs at most 2n comparisons, why the extraction phase makes it Theta(n log n) in every case, its constants against quicksort and merge sort, and its properties (in place, not stable, not adaptive).

Why it exists

Merge sort guarantees nlog⁡nn \log n but needs a second array; quicksort is in place and fast but can degrade to n2n^2. Heap sort gets both guarantees at once: in place and Θ(nlog⁡n)\Theta(n \log n) in the worst case. It is the sort to name when an exam asks for "in place with guaranteed O(nlog⁡n)O(n \log n)". The price is a larger constant than quicksort and no stability.

Prerequisites

The structure and its operations (sift up, sift down, build) are in binary heaps and heap applications. This page adds the analysis as a sorting algorithm and the comparison with the other sorts.

Precise definitions

Max-heap. An array a[1..n]a[1..n] in which a[i]≥a[2i]a[i] \ge a[2i] and a[i]≥a[2i+1]a[i] \ge a[2i + 1] whenever those positions exist; the maximum is a[1]a[1].

Sift down at position ii: while a child is larger than a[i]a[i], swap a[i]a[i] with the larger child and move down. It costs at most two comparisons per level.

Heap sort. (1) Build: for i=⌊n/2⌋,…,1i = \lfloor n/2 \rfloor, \ldots, 1 sift down position ii (the leaves are already heaps). (2) Extract: for e=n,n−1,…,2e = n, n - 1, \ldots, 2: swap a[1]a[1] and a[e]a[e] (the maximum goes to its final place) and sift down position 11 in the heap a[1..e−1]a[1..e-1]. The array ends in increasing order.

How it works

The two animations below are real runs of the C code (the same functions that are used on the heap pages): first the bottom-up build, then the whole sort. In the second, green cells are in their final places.

Building a heap in place: sift down from n / 2 to 1

Real run

C source

1#include <stdio.h>
2
3static void swap(int *x, int *y) {
4 int t = *x;
5 *x = *y;
6 *y = t;
7}
8
9/* a[1..n] is a max-heap except possibly at position i */
10void sift_down(int a[], int n, int i) {
11 for (;;) {
12 int l = 2 * i, r = 2 * i + 1, big = i;
13 if (l <= n && a[l] > a[big]) big = l;
14 if (r <= n && a[r] > a[big]) big = r;
15 if (big == i) break;
16 swap(&a[i], &a[big]);
17 i = big;
18 }
19}
20
21void build_heap(int a[], int n) {
22 for (int i = n / 2; i >= 1; i--) {
23 sift_down(a, n, i);
24 }
25}

Array a (the heap is positions 1 to n; each node's children are at 2i and 2i + 1)

411233241659610714889710
4
1
1
2
3
3
2
4
16
5
9
6
10
7
14
8
8
9
7
10

State

n = 10, swaps so far = 0

Blue = the position this step is working on, amber = a position being compared, red = positions just swapped, green = finished or in its final place, a filled blue cell = a value just placed, faded = not part of the structure at this step.

Step 1

An arbitrary array of 10 keys, viewed as a complete binary tree. Positions 6 to 10 are leaves and are already heaps of size 1.

1 / 26
The array 4, 1, 3, 2, 16, 9, 10, 14, 8, 7 (positions 1 to 10) is set up by hidden driver code. build_heap(), sift_down() and swap() are the exact code that runs. Produced by: real execution: every array, stack, matrix and list in the picture is read from the running program's memory at each step. gcc (Debian 14.2.0-19) 14.2.0. gcc -O0 -o prog prog.c && setarch x86_64 -R ./prog

Heapsort: build a max-heap, then move the maximum to the end again and again

Real run

C source

1#include <stdio.h>
2
3static void swap(int *x, int *y) {
4 int t = *x;
5 *x = *y;
6 *y = t;
7}
8
9/* a[1..n] is a max-heap except possibly at position i */
10void sift_down(int a[], int n, int i) {
11 for (;;) {
12 int l = 2 * i, r = 2 * i + 1, big = i;
13 if (l <= n && a[l] > a[big]) big = l;
14 if (r <= n && a[r] > a[big]) big = r;
15 if (big == i) break;
16 swap(&a[i], &a[big]);
17 i = big;
18 }
19}
20
21void heap_sort(int a[], int n) {
22 for (int i = n / 2; i >= 1; i--) {
23 sift_down(a, n, i);
24 }
25 for (int last = n; last >= 2; last--) {
26 swap(&a[1], &a[last]);
27 sift_down(a, last - 1, 1);
28 }
29}

Array a (the heap is positions 1 to n; each node's children are at 2i and 2i + 1)

51328314952677
5
1
3
2
8
3
1
4
9
5
2
6
7
7

State

n = 7, swaps so far = 0

Blue = the position this step is working on, amber = a position being compared, red = positions just swapped, green = finished or in its final place, a filled blue cell = a value just placed, faded = not part of the structure at this step.

Step 1

An arbitrary array of 7 keys. First it is turned into a max-heap.

1 / 30
The array 5, 3, 8, 1, 9, 2, 7 (positions 1 to 7) is set up by hidden driver code. heap_sort(), sift_down() and swap() are the exact code that runs. Green cells are in their final sorted place. Produced by: real execution: every array, stack, matrix and list in the picture is read from the running program's memory at each step. gcc (Debian 14.2.0-19) 14.2.0. gcc -O0 -o prog prog.c && setarch x86_64 -R ./prog

Cost of building. A node of height hh (the longest path down to a leaf) is sifted down at most hh levels, two comparisons per level. A heap of nn nodes has at most ⌈n/2h+1⌉\lceil n / 2^{h+1} \rceil nodes of height hh, so the total number of comparisons is about

∑h≥0n2h+1⋅2h  =  n∑h≥1h2h  =  2n\sum_{h \ge 0} \frac{n}{2^{h+1}} \cdot 2h \;=\; n \sum_{h \ge 1} \frac{h}{2^h} \;=\; 2n

More precisely the sum of the heights of all nodes is n−(number of 1 bits of n)n - (\text{number of 1 bits of } n), so building uses at most 2(n−popcount(n))2(n - \text{popcount}(n)) comparisons, less than 2n2n: building is Θ(n)\Theta(n), not nlog⁡nn \log n. Inserting the keys one by one (sift up) has a worst case of Θ(nlog⁡n)\Theta(n \log n).

Cost of extracting. There are n−1n - 1 extractions. Each sifts the new root down at most ⌊log⁡2n⌋\lfloor \log_2 n \rfloor levels, two comparisons per level: at most 2⌊log⁡2n⌋2 \lfloor \log_2 n \rfloor comparisons each, so at most 2n⌊log⁡2n⌋2 n \lfloor \log_2 n \rfloor in all. The extraction phase must be Ω(nlog⁡n)\Omega(n \log n) in the worst case: heap sort is a comparison sort, which needs Ω(nlog⁡n)\Omega(n \log n) comparisons (lower bound), and the build takes only O(n)O(n). In practice the key moved to the root is a former leaf and sinks almost to the bottom.

Total. Θ(n)\Theta(n) for the build plus Θ(nlog⁡n)\Theta(n \log n) for the extractions: Θ(nlog⁡n)\Theta(n \log n) in the best, average and worst case; space O(1)O(1) extra. (If all keys are equal and the sift down uses a strict comparison it stops at once and the sort is linear; the usual statement for exams is Θ(nlog⁡n)\Theta(n \log n) for all cases.)

Measured. The program counts the comparisons of keys in the build (bottom-up and by repeated insertion) and of the complete sort, on a random input for n=15,63,255,1023n = 15, 63, 255, 1023.

#include <stdio.h>
 
static long cmps;
 
static void swap(int *x, int *y) { int t = *x; *x = *y; *y = t; }
 
/* a[1..n]: the subtree at i is made a max-heap, given that both subtrees below it already are */
static void sift_down(int a[], int n, int i) {
    for (;;) {
        int l = 2 * i, r = l + 1, big = i;
        if (l <= n) { cmps++; if (a[l] > a[big]) big = l; }
        if (r <= n) { cmps++; if (a[r] > a[big]) big = r; }
        if (big == i) return;
        swap(&a[i], &a[big]);
        i = big;
    }
}
 
static void sift_up(int a[], int i) {
    while (i > 1) {
        cmps++;
        if (a[i / 2] >= a[i]) return;
        swap(&a[i / 2], &a[i]);
        i /= 2;
    }
}
 
static long by_build(int a[], int n) {            /* bottom-up: sift down from n/2 to 1 */
    cmps = 0;
    for (int i = n / 2; i >= 1; i--) sift_down(a, n, i);
    return cmps;
}
 
static long by_insert(int a[], int n) {           /* n insertions, each sifted up */
    cmps = 0;
    for (int i = 2; i <= n; i++) sift_up(a, i);
    return cmps;
}
 
static long sort_all(int a[], int n) {            /* heap sort: build, then n - 1 extractions */
    by_build(a, n);
    for (int e = n; e > 1; e--) { swap(&a[1], &a[e]); sift_down(a, e - 1, 1); }
    for (int i = 2; i <= n; i++) if (a[i - 1] > a[i]) return -1;
    return cmps;
}
 
int main(void) {
    static int a[1100], b[1100];
    int ns[] = {15, 63, 255, 1023};
    unsigned s = 4242;
    printf("%5s %8s %8s %9s\n", "n", "build", "insert", "heapsort");
    for (int q = 0; q < 4; q++) {
        int n = ns[q];
        for (int i = 1; i <= n; i++) { a[i] = i; }
        for (int i = n; i > 1; i--) { s = s * 1103515245u + 12345u; int j = 1 + (int)((s >> 8) % (unsigned)i); swap(&a[i], &a[j]); }
        for (int i = 1; i <= n; i++) b[i] = a[i];
        long cb = by_build(a, n);
        for (int i = 1; i <= n; i++) a[i] = b[i];
        long ci = by_insert(a, n);
        for (int i = 1; i <= n; i++) a[i] = b[i];
        long cs = sort_all(a, n);
        printf("%5d %8ld %8ld %9ld\n", n, cb, ci, cs);
    }
    return 0;
}
    n    build   insert  heapsort
   15       22       21        76
   63      112      127       564
  255      472      551      3281
 1023     1890     2323     17292

Comparisons of heap construction and heap sort, measured: start

Computed by a script, not typed
nbottom-up buildper keyn insertionsper keycomplete heap sortper n log2 n
15
63
255
1023

Step 0

One random input for each n = 2^k - 1. Bottom-up construction (sift down from n/2 to 1) is compared with building by n insertions, and with the whole heap sort. The program is on this page.

0 / 4
computed by script: the quantities are measured by running C, or evaluated, one row at a time. python3 scripts/gen-algo-sort2-traces.py

Bottom-up building stayed below 22 comparisons per key. (In this single run repeated insertion was cheaper for the smallest input and dearer for the larger ones; its guaranteed worst case is Θ(nlog⁡n)\Theta(n \log n), while bottom-up is guaranteed linear.) The whole sort used between 1.31.3 and 1.71.7 times nlog⁡2nn \log_2 n comparisons, rising with nn and never above the bound 2nlog⁡2n2 n \log_2 n; for the larger nn that is more than the 1.39 nlog⁡2n1.39\, n \log_2 n average of quicksort (quicksort) and than merge sort's nlog⁡2nn \log_2 n (merge sort).

Properties.

  • In place: only a few variables besides the array.
  • Not stable: the root is swapped with the last leaf, a long jump that ignores equal keys (the measured test of stability and comparison of sorts shows 31493149 of 32793279 small arrays reordered).
  • Not adaptive: already sorted input costs the same as random input.
  • Cache behaviour: the parent and children are far apart in memory, so heap sort is slower in practice than merge sort or quicksort, which scan memory sequentially.
  • A max-heap produces increasing order. Using a min-heap and writing the extracted keys to a new array (or to the end of a reversed array) gives decreasing order.

Heaps for partial answers. The kk largest of nn keys: build a max-heap and extract kk times, Θ(n+klog⁡n)\Theta(n + k \log n); or keep a min-heap of the kk largest seen so far, Θ(nlog⁡k)\Theta(n \log k). Both beat sorting when kk is small.

Worked examples

EX 1Build a max-heap

Easy

Array 3,9,2,1,4,53, 9, 2, 1, 4, 5 (n=6n = 6). Sift down positions 3,2,13, 2, 1. Position 33 (key 22) has the single child at position 66 (key 55): swap, giving 3,9,5,1,4,23, 9, 5, 1, 4, 2. Position 22 (key 99) is larger than its children 1,41, 4: no change. Position 11 (key 33): the larger child is 99 at position 22: swap, 9,3,5,1,4,29, 3, 5, 1, 4, 2; the key 33 is now at position 22 with children 1,41, 4: swap with 44, 9,4,5,1,3,29, 4, 5, 1, 3, 2. Result: the heap 9,4,5,1,3,29, 4, 5, 1, 3, 2 after 33 swaps. The number of swaps is at most the sum of heights, 6−2=46 - 2 = 4 (6=11026 = 110_2 has two 1 bits).

EX 2One extraction

Easy

From the heap 9,4,5,1,3,29, 4, 5, 1, 3, 2: swap the root with the last key: 2,4,5,1,3 ∣ 92, 4, 5, 1, 3 \,|\, 9 (99 is final). Sift down the root in the heap of size 55: the children are 44 and 55, the larger is 55: swap, 5,4,2,1,35, 4, 2, 1, 3. The key 22 is at position 33, whose children would be at positions 66 and 77, outside the heap: stop. Two comparisons were made.

EX 3The bound on comparisons

Medium

For n=1023=210−1n = 1023 = 2^{10} - 1 the build takes at most 2(1023−10)=20262(1023 - 10) = 2026 comparisons (the measured 18901890 is below it) and the extraction phase at most 2⋅1022⋅9=18 3962 \cdot 1022 \cdot 9 = 18\,396 more, so heap sort uses at most about 2nlog⁡2n2 n \log_2 n; the measured total on a random input was 17 29217\,292. Merge sort's worst case for 10231023 keys is about 9 2009\,200.

EX 4Which sort does the job?

Medium

Sort 10610^6 keys on a device with memory for only a few extra values and a requirement that the worst case be O(nlog⁡n)O(n \log n): heap sort (merge sort needs a second array; quicksort may reach n2n^2). The same but equal keys must keep their order: none of the three comparison sorts qualifies without extra memory; attach the original index as a tiebreaker (adds Θ(n)\Theta(n) memory) or accept merge sort's array.

EX 5True or false about heap sort

GATE level

(a) The time to build a heap from an arbitrary array of nn keys is Θ(nlog⁡n)\Theta(n \log n): false, Θ(n)\Theta(n). (b) Heap sort is O(nlog⁡n)O(n \log n) in the worst case: true. (c) Heap sort takes O(n)O(n) time on sorted input: false, Θ(nlog⁡n)\Theta(n \log n) (it is not adaptive). (d) Heap sort is stable: false. (e) The smallest key of a max-heap of distinct keys is in one of the leaves: true, the leaves are positions ⌊n/2⌋+1,…,n\lfloor n/2 \rfloor + 1, \ldots, n. (f) Finding the largest key of a max-heap: Θ(1)\Theta(1).

Cheat box

Cheat box

  • Build: sift down from ⌊n/2⌋\lfloor n/2 \rfloor to 11; Θ(n)\Theta(n), at most 2(n−popcount(n))<2n2(n - \text{popcount}(n)) < 2n comparisons.
  • Extract: n−1n - 1 times swap root with the last key, shrink, sift down: at most 2⌊log⁡2n⌋2 \lfloor \log_2 n \rfloor comparisons each.
  • Total Θ(nlog⁡n)\Theta(n \log n) in best, average and worst case; at most about 2nlog⁡2n2 n \log_2 n comparisons.
  • In place (O(1)O(1)), not stable, not adaptive; poor cache behaviour.
  • Max-heap gives increasing order.
  • kk largest keys: Θ(n+klog⁡n)\Theta(n + k \log n) or Θ(nlog⁡k)\Theta(n \log k).

Traps and GATE patterns

Connections

The structure is in binary heaps and its uses in heap applications, where the same two animations appear. The comparison with the other sorts is in stability and comparison of sorts; the competing methods are merge sort and quicksort. Priority queues built on heaps drive Dijkstra's algorithm and Huffman coding.

PYQs

3 previous year questions from your book. I re-solved each one myself (2 by running or simulating, 1 by reasoning only); the book's key is shown next to my answer. All PYQs

GATE 2015 · Set 2 · MCQ · 1 markQ20 · page 100 of the book
Consider a complete binary tree where the left and
the right subtrees of the root are max heaps. The
lower bound for the number of operations to
convert the tree to a heap is
  • (a)Ω(logn)
  • (b)Ω(n)
  • (c)Ω(nlogn)
  • (d)Ω(n2)
GATE 2017 · Set 2 · MCQ · 2 marksQ5 · page 146 of the book
Match the algorithms with their time complexities:
Algorithm
Time
complexity
P Towers of Hanoi with
n disks
i
Θ(n2 )
Q Binary search given n
sorted numbers
ii
Θ(nlog n)
R Heap sort given n
numbers at the worst
case
iii
Θ(2n )
S
Addition of two n × n
matrices
iv
Θ(logn)
ALGORITHMS

Check Out Our Gate Courses & Secure Your Dream Rank !
  • (a)P-(iii), Q-(iv), R-(i), S-(ii)
  • (b)P-(iv), Q-(iii), R-(i), S-(ii)
  • (c)P-(iii), Q-(iv), R-(ii), S-(i)
  • (d)P-(iv), Q-(iii), R-(ii), S-(i)
GATE 2013 · MCQ · 1 markQ9 · page 148 of the book
The number of elements that can be sorted in
Θ(log n) time using heap sort is
  • (a)θ(1)
  • (b)θ(√log n)
  • (c)θ(log n / (loglog n))
  • (d)θ(log n)

Self-check

Question 1

What is the time to build a max-heap from nn keys in an array, and why not nlog⁡nn \log n?

Show answer

Θ(n)\Theta(n): most nodes are near the bottom and sift down only a few levels; the sum of the heights is less than nn.

Question 2

What is the best-case time of heap sort on a sorted array of nn distinct keys?

Show answer

Θ(nlog⁡n)\Theta(n \log n): heap sort is not adaptive.

Question 3

Name two properties of heap sort that make it preferable to quicksort and merge sort in some settings.

Show answer

It is in place (unlike merge sort on arrays) and has a Θ(nlog⁡n)\Theta(n \log n) worst case (unlike quicksort).

Question 4

Is heap sort stable?

Show answer

No.

Revision card

One-screen revision

  • Build Θ(n)\Theta(n) (<2n< 2n comparisons) + n−1n - 1 extractions of ≤2lg⁡n\le 2 \lg n comparisons: Θ(nlog⁡n)\Theta(n \log n) always.
  • In place, not stable, not adaptive; max-heap →\to increasing.
  • ≈2nlg⁡n\approx 2 n \lg n comparisons worst case, more than quicksort's average.
  • kk largest: Θ(n+klog⁡n)\Theta(n + k \log n).

References

  • ·Cormen, Leiserson, Rivest, Stein, Introduction to Algorithms (3rd edition), Chapter 6 Heapsort (6.3 building a heap, 6.4 the heapsort algorithm)
  • ·Sedgewick and Wayne, Algorithms (4th edition), Section 2.4 Priority Queues (heapsort)

Verification

The heap structure, the two animations and the data-structure side of heapsort are on the page of heap applications (draft, machine-checked). Here the cost was re-measured: heap construction and heap sort were re-implemented in Python and run on EVERY permutation of up to 8 keys (bottom-up construction never needed more than 2(n - popcount(n)) comparisons, and the whole sort never more than 2 n floor(lg n)) and on 200 random inputs of up to 400 keys; all-equal keys with the strict comparison need a linear number of comparisons. The C program on this page produced the table of measured comparisons. The textbook section numbers are from memory. SECOND PASS (2026-11-02, done by the assistant, not a human check): every Self-check question was solved before the printed answer was read and all agreed; the definitions, worked examples, cheat box and traps were read claim by claim and the numbers re-derived by hand or by a small script (loop counts, recurrences, merge and quicksort counts and averages, hash probes and load-factor formulas, DP tables, MST and shortest-path runs, Floyd matrices); long C programs were not re-read line by line (they are compiled and compared by npm run verify:c, and their printed outputs were checked against the prose); tables and animations are machine output and were read, not re-run; No other defect found. Status raised to self-checked; verified needs a textbook or human check.

last verified: never