G27
Self-checkedTier 1 13 min readalgo.sorting-lower-bound-linear

Lower bound and linear-time sorts

Why no comparison sort can beat n log n (the decision tree argument with n! leaves), how counting sort, radix sort and bucket sort sort in linear time by using the values of the keys instead of comparisons, their costs Theta(n + k), Theta(d(n + k)) and expected Theta(n), why stability makes radix sort correct, and when each applies.

Why it exists

Merge sort and heap sort take Θ(nlog⁡n)\Theta(n \log n). Can any algorithm do better? The answer has two halves, and both are exam material. No, if the only thing the algorithm may do with the keys is compare two of them: Ω(nlog⁡n)\Omega(n \log n) comparisons are unavoidable. Yes, if the keys are small integers (or digits, or uniformly spread numbers): then counting sort, radix sort and bucket sort run in linear time, by looking inside the keys instead of comparing them.

Prerequisites

You need the comparison sorts (merge sort, quicksort) and the idea of stability (stability and comparison of sorts).

Precise definitions

Comparison sort model. The algorithm learns about the keys only through questions "is ai<aja_i < a_j?" with a yes/no answer. Every comparison sort in the syllabus (bubble, selection, insertion, merge, quick, heap) is of this kind.

Decision tree. For a fixed nn, the behaviour of a comparison sort can be drawn as a binary tree: each internal node is a comparison of two positions, its two children correspond to the two answers, and each leaf is a final answer, namely a permutation that sorts the input. The number of comparisons on an input is the depth of the leaf reached; the worst case is the height of the tree.

Counting sort. For integer keys in 0,…,k0, \ldots, k: count how often each key occurs, turn the counts into end positions by prefix sums, and place the keys into the output by scanning the input from the last key to the first.

Radix sort (LSD). For keys of dd digits in base bb: for the digit positions from the least significant to the most significant, sort the whole array by that digit with a stable sort (counting sort).

Bucket sort. For keys spread uniformly over [0,1)[0, 1): distribute the nn keys into nn buckets (key xx to bucket ⌊nx⌋\lfloor n x \rfloor), sort each bucket (insertion sort), and concatenate.

How it works

The lower bound

Claim. Every comparison sort needs at least ⌈log⁡2n!⌉=Ω(nlog⁡n)\lceil \log_2 n! \rceil = \Omega(n \log n) comparisons in the worst case.

Proof. If the nn keys are distinct there are n!n! possible orders, and a correct algorithm must reach a different leaf for each (two different orders need different permutations as output). So the decision tree has at least n!n! leaves. A binary tree of height hh has at most 2h2^h leaves, so 2h≥n!2^h \ge n!, i.e. h≥log⁡2n!h \ge \log_2 n!. By Stirling's approximation n!>(n/e)nn! > (n/e)^n, hence h  ≥  log⁡2n!  >  nlog⁡2n−nlog⁡2e  =  Ω(nlog⁡n).h \;\ge\; \log_2 n! \;>\; n \log_2 n - n \log_2 e \;=\; \Omega(n \log n). The same argument applies to the average number of comparisons: the average depth of n!n! leaves of a binary tree is at least log⁡2n!\log_2 n!. So no comparison sort has o(nlog⁡n)o(n \log n) average or worst case. ■\blacksquare

Consequences: merge sort and heap sort are asymptotically optimal comparison sorts; quicksort is optimal on average. The table shows how the bound compares with two real algorithms for small nn.

How few comparisons can sorting need?: start

Computed by a script, not typed
nn! (possible orders)ceil(log2 n!) (lower bound)binary insertion sort (worst)merge sort (worst)n log2 n - 1.4427 n
1
2
3
4
5
6
7
8
9
10
11
12

Step 0

A comparison sort must distinguish all n! orders; each comparison has two outcomes, so it needs at least ceil(log2 n!) comparisons in the worst case. Two real algorithms are shown above that bound.

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

For n≤5n \le 5 the bound is exactly attained: an exhaustive search over all strategies gives the minimum worst-case numbers of comparisons 0,1,3,5,70, 1, 3, 5, 7, which equal ⌈log⁡2n!⌉\lceil \log_2 n! \rceil. (Only the order nlog⁡nn \log n matters for the syllabus; whether the bound is attained exactly for every nn is not needed.) The same method gives other lower bounds, for example that finding the maximum of nn keys needs n−1n - 1 comparisons: every key except the maximum must lose at least one comparison.

What the bound does not cover. An algorithm that uses the values of the keys (as an array index, as a digit) is not a comparison sort. The bound says nothing about it.

Counting sort

The animation shows the three passes on keys between 00 and 44: count each key, add up the counts so that count[v]\text{count}[v] becomes the position just after the last key vv, then place the keys backwards. The code is a real run.

Counting sort: count the keys, add up the counts, place the keys backwards

Real run

C source

1#include <stdio.h>
2
3/* sorts the n keys of a, each between 0 and k (k < 8), into out; stable */
4void counting_sort(const int a[], int n, int out[], int k) {
5 int count[8] = {0};
6 for (int i = 0; i < n; i++) {
7 count[a[i]]++;
8 }
9 for (int v = 1; v <= k; v++) {
10 count[v] += count[v - 1];
11 }
12 for (int i = n - 1; i >= 0; i--) {
13 out[--count[a[i]]] = a[i];
14 }
15}

Input a

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

count[v] for v = 0 to 4

0
0
0
1
0
2
0
3
0
4

Output out

0
1
2
3
4
5
6
7

Phase

phase 0 of 3: place the keys, last to first

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

Sort the keys 3, 1, 4, 1, 3, 0, 4, 1, which are all between 0 and 4, with counting sort.

1 / 23
The eight keys 3, 1, 4, 1, 3, 0, 4, 1 (all between 0 and 4) are set up by hidden driver code. counting_sort() is the exact code that runs. The lower rows are the count array and the output array. Produced by: real execution: every array, stack, matrix and list in the picture is read from the running program's memory at each step. Apple clang version 21.0.0 (clang-2100.1.1.101). gcc -O0 -o prog prog.c && ./prog

Cost. The three loops run nn, kk and nn times: Θ(n+k)\Theta(n + k) time, and Θ(n+k)\Theta(n + k) extra space (the count array of k+1k + 1 cells and the output array). It is linear when k=O(n)k = O(n), but if kk is much larger than nn (for example k=n2k = n^2) it is worse than a comparison sort. It makes no comparison between keys. It is stable: scanning backwards puts the last of several equal keys last, and equal keys end up in input order.

Radix sort

Sorting by the least significant digit first and then by each more significant digit works because each pass is stable: after the pass for digit jj the numbers are ordered by their last jj digits, since a stable sort by digit jj keeps the order by the lower digits among numbers with equal digit jj. The program sorts eight 3-digit numbers with one counting-sort pass per digit.

#include <stdio.h>
 
/* one stable counting-sort pass on the decimal digit that has the value place (1, 10, 100) */
static void pass_by_digit(int a[], int n, int place) {
    int count[10] = {0}, out[16];
    for (int i = 0; i < n; i++) count[(a[i] / place) % 10]++;
    for (int d = 1; d < 10; d++) count[d] += count[d - 1];
    for (int i = n - 1; i >= 0; i--) out[--count[(a[i] / place) % 10]] = a[i];
    for (int i = 0; i < n; i++) a[i] = out[i];
}
 
int main(void) {
    int a[8] = {493, 217, 826, 305, 118, 652, 270, 903};
    printf("start     ");
    for (int i = 0; i < 8; i++) printf(" %3d", a[i]);
    printf("\n");
    int place = 1;
    for (int d = 1; d <= 3; d++, place *= 10) {
        pass_by_digit(a, 8, place);
        printf("digit %d   ", d);
        for (int i = 0; i < 8; i++) printf(" %3d", a[i]);
        printf("\n");
    }
    return 0;
}
start      493 217 826 305 118 652 270 903
digit 1    270 652 493 903 305 826 217 118
digit 2    903 305 217 118 826 652 270 493
digit 3    118 217 270 305 493 652 826 903

Radix sort, one digit at a time: start

Computed by a script, not typed
stage01234567
start
after digit 1 (units)
after digit 2 (tens)
after digit 3 (hundreds)

Step 0

Least significant digit first: each pass is a stable counting sort on one decimal digit. After the pass for digit d the numbers are sorted by their last d digits.

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

In the first pass 493493 and 903903 have the same units digit and stay in the order 493,903493, 903; that is stability at work.

Cost. Each of the dd passes is a counting sort with k=b−1k = b - 1: Θ(d(n+b))\Theta(d (n + b)) in total. Choosing the base: nn numbers each below ncn^c can be written with cc digits in base nn, so radix sort takes cc passes of Θ(n)\Theta(n): Θ(c n)\Theta(c\,n), linear for constant cc. For example nn integers in 0…n2−10 \ldots n^2 - 1 take two passes in base nn, while plain counting sort would take Θ(n2)\Theta(n^2). For ww-bit keys and digits of rr bits the cost is Θ(wr(n+2r))\Theta\big(\tfrac{w}{r}(n + 2^r)\big).

If an unstable digit sort were used, the order produced by earlier passes would be destroyed and the result could be wrong. (Most-significant-digit-first variants exist; they recurse on groups of equal leading digits and do not need the same stability argument.)

Bucket sort

If the nn keys are drawn independently and uniformly from [0,1)[0, 1), each bucket receives about one key. With insertion sort inside the buckets the expected time is Θ(n)\Theta(n): the expected cost of a bucket with mm keys is Θ(m2)\Theta(m^2) and the expected value of m2m^2 for a bucket is a constant (about 2−1/n2 - 1/n). The worst case is Θ(n2)\Theta(n^2) (all keys fall into one bucket) if insertion sort is used inside. It is stable if the buckets are stable lists and the sort inside is stable. It depends on the distribution of the input, not only on nn.

Summary.

SortTimeExtra spaceConditions
Any comparison sortΩ(nlog⁡n)\Omega(n \log n) worst and averageonly comparisons allowed
Counting sortΘ(n+k)\Theta(n + k)Θ(n+k)\Theta(n + k)integer keys in 0…k0 \ldots k
Radix sortΘ(d(n+b))\Theta(d(n + b))Θ(n+b)\Theta(n + b)dd digits in base bb, stable digit sort
Bucket sortexpected Θ(n)\Theta(n), worst Θ(n2)\Theta(n^2)Θ(n)\Theta(n)keys uniform on an interval

Worked examples

EX 1Minimum comparisons for small n

Easy

For 33 keys there are 3!=63! = 6 orders, so at least ⌈log⁡26⌉=3\lceil \log_2 6 \rceil = 3 comparisons are needed in the worst case, and 33 suffice (the exhaustive search of the next paragraph confirms it). For 55 keys: 5!=1205! = 120, ⌈log⁡2120⌉=7\lceil \log_2 120 \rceil = 7, and 77 comparisons are indeed enough (a search over all strategies confirms that 77 is the exact minimum). For 44 keys: 2424 orders, bound 55.

EX 2Counting sort by hand

Easy

Keys 3,1,4,1,3,0,4,13, 1, 4, 1, 3, 0, 4, 1 (k=4k = 4). Counts for 0,1,2,3,40, 1, 2, 3, 4: 1,3,0,2,21, 3, 0, 2, 2. Prefix sums: 1,4,4,6,81, 4, 4, 6, 8. Now scan from the last key: the last key 11 goes to position 4−1=34 - 1 = 3 and the count of 11 becomes 33; the next key 44 goes to position 77; 00 to position 00; and so on, ending with the output 0,1,1,1,3,3,4,40, 1, 1, 1, 3, 3, 4, 4. The three equal 11s were placed last-to-first, so they appear in their input order.

EX 3Choosing the base for radix sort

Medium

Sort n=1000n = 1000 integers, each less than 10910^9. In base 1010 there are 99 digits: 99 passes of Θ(n+10)\Theta(n + 10). In base 10001000 there are 33 digits (109=1000310^9 = 1000^3): 33 passes of Θ(n+1000)\Theta(n + 1000), about 3⋅2000=60003 \cdot 2000 = 6000 steps of the counting loops, against about 9⋅1010≈91009 \cdot 1010 \approx 9100 in base 1010 (constants ignored). Plain counting sort would need a count array of 10910^9 cells. A comparison sort needs ≈nlog⁡2n≈104\approx n \log_2 n \approx 10^4 comparisons: the same order as radix sort here, since c=3c = 3 is not far from log⁡2n≈10\log_2 n \approx 10.

EX 4Why the digit sort must be stable

Medium

Sort 21,11,25,1521, 11, 25, 15 by least-significant-digit radix sort. The units pass (stable) leaves the order 21,11,25,1521, 11, 25, 15 (units 1,1,5,51, 1, 5, 5). The tens pass must keep the order of numbers with the same tens digit: stable, it gives 11,15,21,2511, 15, 21, 25, which is sorted. An unstable tens pass is free to put 1515 before 1111 and 2525 before 2121 in other ways, for example 15,11,25,2115, 11, 25, 21, which is not sorted: the order established by the units pass was lost. The stability of each later pass carries the work of the earlier ones.

EX 5Which statements hold?

GATE level

(a) Any comparison-based sort needs Ω(nlog⁡n)\Omega(n \log n) comparisons in the worst case: true. (b) Merge sort is an optimal comparison sort: true, asymptotically. (c) Counting sort breaks the lower bound: it is not a comparison sort, so it does not contradict it. (d) Sorting nn integers in 0…n3−10 \ldots n^3 - 1 in O(n)O(n) time: yes, radix sort with base nn and three counting-sort passes. (e) Counting sort with k=n2k = n^2: Θ(n2)\Theta(n^2), worse than nlog⁡nn \log n. (f) Radix sort with an unstable digit sort: may fail. (g) Bucket sort's worst case: Θ(n2)\Theta(n^2) with insertion sort inside the buckets.

Cheat box

Cheat box

  • Decision tree: ≥n!\ge n! leaves, height ≥⌈log⁡2n!⌉=Ω(nlog⁡n)\ge \lceil \log_2 n! \rceil = \Omega(n \log n) (worst and average). Merge and heap sort are optimal.
  • Counting sort: keys 0…k0 \ldots k, Θ(n+k)\Theta(n + k) time and space, stable (backward pass), no comparisons.
  • Radix sort (LSD): Θ(d(n+b))\Theta(d(n + b)), needs a stable digit sort; nn keys below ncn^c: base nn, cc passes, Θ(n)\Theta(n).
  • Bucket sort: uniform keys, expected Θ(n)\Theta(n), worst Θ(n2)\Theta(n^2).
  • Minimum comparisons to sort n≤5n \le 5 keys: 0,1,3,5,70, 1, 3, 5, 7.
  • Finding the maximum needs n−1n - 1 comparisons.

Traps and GATE patterns

Connections

The upper bounds that match this lower bound are merge sort and heap sort; quicksort matches it on average (quicksort). Stability, which makes radix sort correct, is compared across algorithms in stability and comparison of sorts. The idea of using the key value as an index is the idea of hashing.

PYQs

No PYQs attached to this concept yet

Previous year questions are being added section by section from your PYQ book, each with my own re-checked answer. Nothing is shown from memory.

Self-check

Question 1

What is the minimum number of comparisons needed in the worst case to sort 44 distinct keys?

Show answer

At least ⌈log⁡224⌉=5\lceil \log_2 24 \rceil = 5, and 55 is exactly the minimum (found by exhaustive search).

Question 2

Why does counting sort not contradict the Ω(nlog⁡n)\Omega(n \log n) bound?

Show answer

It is not a comparison sort: it uses the key values as array indices and never compares two keys.

Question 3

How many passes and what total time does radix sort need for nn integers in 0…n2−10 \ldots n^2 - 1?

Show answer

Two passes in base nn, each a counting sort of Θ(n)\Theta(n): Θ(n)\Theta(n) in all.

Question 4

What goes wrong if radix sort uses an unstable digit sort?

Show answer

Equal digits can be reordered, destroying the order established by the less significant digits, so the final array may not be sorted.

Revision card

One-screen revision

  • Comparison sort: decision tree with ≥n!\ge n! leaves, height ≥⌈log⁡2n!⌉=Ω(nlog⁡n)\ge \lceil \log_2 n! \rceil = \Omega(n \log n); worst and average; n≤5n \le 5: 0,1,3,5,70, 1, 3, 5, 7 exact.
  • Counting: Θ(n+k)\Theta(n + k), stable. Radix: Θ(d(n+b))\Theta(d(n + b)), LSD, stable passes; base nn for keys below ncn^c.
  • Bucket: expected Θ(n)\Theta(n) uniform, worst Θ(n2)\Theta(n^2).
  • Not comparison based: no contradiction.

References

  • ·Cormen, Leiserson, Rivest, Stein, Introduction to Algorithms (3rd edition), Chapter 8 Sorting in Linear Time (8.1 lower bounds, 8.2 counting sort, 8.3 radix sort, 8.4 bucket sort)
  • ·Sedgewick and Wayne, Algorithms (4th edition), Section 2.5 and Section 5.1 String Sorts (key-indexed counting, LSD radix sort)
  • ·Knuth, The Art of Computer Programming, Volume 3, Section 5.3.1 Minimum-comparison sorting; Section 5.2.5 Sorting by distribution

Verification

The decision tree bound was backed by an exhaustive game search (every strategy of comparing pairs, for every set of consistent orders): the exact minimum worst-case number of comparisons for n = 1 to 5 is 0, 1, 3, 5, 7, equal to ceil(log2 n!) in each case; the table of the bound was recomputed with exact integers, and the Stirling-type inequalities were checked numerically. Counting sort was tested on every array of up to 6 keys from {0, 1, 2, 3} against a stable reference; radix sort on 500 random inputs, including the claim that keys below n^3 need three passes in base n; the radix table was replayed by independent code. The expected linear time of bucket sort for uniform keys was measured by simulation; its proof is the standard one from the textbook. Textbook chapter references 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