Learn
Lesson · Sorting and Selection · Not comparing at all

Radix Sort

A key too wide for one counting sort can be split into d digits in base b and sorted one digit at a time, least significant first, each pass a stable counting sort over b buckets. The cost is Θ(d(n + b)), and the reason the passes compose into a sorted array is not arithmetic: it is that every pass leaves its own ties in the order the pass before it produced.

for digit position 0, 1, …, d−1: stable counting sort on that digit least significant digit FIRST, and every pass stable work = d(n + b) 7 keys, base 10, 3 digits: 3 × 17 = 51 the same keys in base 2: 10 × 9 = 90 one unstable pass undoes every pass before it
The loop, the cost, and the property the whole thing rests on

Core ideas

What this lesson establishes

The hard idea is the invariant across passes, and it is where a reader's intuition about significance runs exactly backwards.

1

After the pass on digit d, the array is sorted on digits 0 through d

That is the invariant, and it is what has to be preserved. Pass d puts the keys in order of digit d. Two keys that agree in digit d are left in the order the previous pass gave them, which by the invariant was order on digits 0 … d−1. So they come out ordered on digits 0 … d, which is the invariant again one digit wider. After the last pass the array is sorted on the whole key.

2

Stability is the composition, not a nicety

The step above used exactly one property of the pass: that it leaves its ties alone. Take it away and the argument has nothing. The lab supplies a variant whose passes reverse their buckets, and on the standard seven keys the result is 355 329 457 436 657 720 839 — not sorted, and the pass that broke it is any of them.

3

Most significant first, with no recursion, sorts by the last pass alone

Running the digits from the most significant down feels right and is wrong unless each bucket is then sorted recursively. Without that, the final pass is the one on the units digit and it rearranges the whole array, so everything the earlier passes arranged is overwritten. The lab's output is 720 355 436 457 657 329 839, whose units digits read 0 5 6 7 7 9 9: sorted on the least significant digit and on nothing else.

The material

One counting sort per digit, and the two ways to break the composition

The passes, the invariant that makes them compose, and the two variants the lab lets you watch fail.

Definition · LSD radix sort

Least-significant-digit radix sort sorts n keys, each of d digits in base b, by performing d passes. Pass i is a stable counting sort of the whole array on digit i, counting digits from the least significant, which is digit zero. After the last pass the array is sorted.

Each pass is counting sort with k = b, so a pass costs n + b and the whole sort costs d(n + b). Nothing is compared, so the comparison lower bound is as silent here as it was there: on seven keys it says 13 comparisons are necessary for any comparison sort, and this algorithm makes none.

Example · Seven three-digit keys, base ten

The keys are 329 457 657 839 436 720 355. Three passes, 17 units of work each, 51 in all. Pass one reads the units digit, pass two the tens, pass three the hundreds, and the array after each pass is in the worked example below. Only after the third pass is it sorted, and after the first two it is sorted on a suffix of the key.

Theorem · The passes compose

If every pass is a stable sort on its digit, then after the pass on digit i the array is in increasing order of the number formed by digits 0 through i of each key.

By induction on i. Before any pass, the claim is about no digits at all and holds vacuously.

Suppose it holds after the pass on digit i−1, and run a stable pass on digit i. Take two keys x and y with x before y in the output. If their digits i differ, the pass placed them in increasing order of that digit, and digit i outranks all the digits below it, so the numbers formed by digits 0 … i are in increasing order.

If their digits i agree, the pass left them in the order it found them, which by the inductive hypothesis was increasing order of digits 0 … i−1. Since their digits i are equal, that is increasing order of digits 0 … i as well. The claim holds after pass i, and after pass d−1 it is the whole key.

□

Read the second paragraph of that proof again: the only fact used about the pass was stability, and it was used in the case where the current digit decides nothing. That is why an unstable pass does not merely degrade the result — it removes the argument entirely, and the lab shows the array that comes out.

Choosing the base

The base is free and it moves both terms. Larger b means fewer digits d = ⌈log_b U⌉ for keys below U, and a bigger bucket array per pass. The same seven keys cost 3 × (7 + 10) = 51 in base ten, 10 × (7 + 2) = 90 in base two, and 3 × (7 + 16) = 69 in base sixteen. There is no rule that one base always wins; there is a product to evaluate, and the lab evaluates it as you move the control.

The honest form of the cost is therefore Θ(d(n + b)) with d depending on the key width and the base, which is why radix sort is not Θ(n) in any useful sense. For n keys drawn from 0 … n^c − 1 and base b = n, it is Θ(cn), and that statement carries its hypotheses in it.

Example · The two broken variants, side by side

Unstable passes, least significant first: 355 329 457 436 657 720 839 — not sorted. Most significant first with no recursion: 720 355 436 457 657 329 839 — not sorted, and sorted on the units digit alone, because the pass that ran last read the units. The lab checks both against the sorted array rather than asserting failure.

Neither variant is a strawman. The first is what happens when the per-digit sort is replaced by something convenient that happens not to be stable. The second is what happens when a reader reasons from significance: the most important digit should be settled first. Most-significant-digit radix sort is a real and useful algorithm — it recurses into each bucket, which is precisely the step whose absence the lab is demonstrating.

Interactive

Radix sorts by making every pass stable

One counting sort per digit, and two ways to break the composition

Every pass is a counting sort, and every pass must be stablethe digit passes, and the two ways of getting them wrong
this pass's bucketsthe digit being readout of ordersorted

Method

Running the passes, and checking the invariant between them

The check between passes is the whole discipline: after pass i the array is sorted on a suffix of the key, and you can verify that.

1

Fix the base, then count the digits

d = ⌈log_b U⌉ where U bounds the keys. Write down d(n + b) before running anything, and compare it with the same product at another base. This is the sizing step, and it is the one that decides whether radix sort is worth the bucket array.

2

Sort on digit zero, stably

Bucket by the least significant digit, and inside each bucket keep the input order. Concatenating the buckets in order gives the array after pass one.

3

After each pass, check the suffix claim

After pass i, the array must be in order of the last i + 1 digits of each key. This is checkable by eye on seven keys and it is how you find the pass that went wrong, rather than discovering at the end that the output is not sorted.

4

Watch what a pass does inside its own ties

In the base-ten example, the third pass leaves 329 before 355 because both have hundreds digit 3 and the second pass had already put them in that order. That single observation is the correctness proof in miniature; if you can see it once you will not run the passes in the other order.

5

Break it deliberately, once

Switch the variant to unstable passes and then to most-significant-first, and read the outputs. The second one is the more instructive: the array comes out ordered by its units digit, which is the signature of the last pass having overwritten everything.

Passes, and what composes them

Score 0 / 0

Three passes on seven keys, base ten

The keys are the lab's defaults. Each pass buckets the whole array by one digit, keeping the order inside each bucket, and concatenates the buckets from digit zero upwards.

input 329 457 657 839 436 720 355 PASS 1, units digit bucket 0 720 bucket 5 355 bucket 6 436 bucket 7 457, 657 bucket 9 329, 839 after 720 355 436 457 657 329 839 PASS 2, tens digit bucket 2 720, 329 bucket 3 436, 839 bucket 5 355, 457, 657 after 720 329 436 839 355 457 657 PASS 3, hundreds digit bucket 3 329, 355 bucket 4 436, 457 bucket 6 657 bucket 7 720 bucket 8 839 after 329 355 436 457 657 720 839 sorted work 3 passes × (n + b) = 3 × (7 + 10) = 51 units comparisons 0 ⌈log₂ 7!⌉ 13, which every comparison sort needs and this one is outside

The load-bearing line is bucket 3 of pass three. It holds 329 and 355, which agree in the hundreds digit, and it holds them in that order because pass two put 329 before 355 and pass three did not disturb its own ties. Every correctly ordered pair of keys that agree in the hundreds digit is there for the same reason.

Notice also what pass one and pass two produced: arrays that are not sorted and are not meant to be. After pass one the array is in order of units digits; after pass two, in order of the two-digit number formed by tens and units, which you can read off as 20 29 36 39 55 57 57. The invariant is checkable at every stage.

For a faded rehearsal, set the base to 2 and predict two things before looking: the number of passes, and the total work. The supplied first move is that 839 needs ten binary digits, so d = 10; work out d(n + b) and then check it, and say which of base 2, 10 and 16 you would choose for these seven keys and why the answer would change for seven million of them.

Common mistakes

1
Starting from the most significant digitThe intuition is that the important digit should be settled first, and the arithmetic says otherwise: whichever pass runs last determines the order, so the most significant digit must be sorted last. Most-significant-first is a real algorithm, but only with a recursive sort inside each bucket, which is exactly the part the lab omits in order to show what it was doing.
2
Treating the per-digit sort as interchangeableAny sort will order one digit; only a stable one composes. If a pass is implemented with something convenient and unstable, the array can come out sorted on some inputs by luck — and the lab says so on its face, because a variant that sometimes works is harder to distrust than one that never does.
3
Calling radix sort linearΘ(d(n + b)) is linear in n for a fixed d and b, and d is ⌈log_b U⌉, which depends on how wide the keys are. Quoting Θ(n) hides two choices and one property of the data. State the number of passes and the base, as the lab's two counters do.

Check your understanding

✓
Finish when you can say which property of a pass the correctness argument uses, and check it between passes.

You should be able to run three digit passes by hand, verify after each one that the array is sorted on the digits seen so far, compute d(n + b) at two bases, and explain both failure modes from the induction rather than from the output.

Note: Counting sort and radix sort escape the comparison bound by reading the key. “Bucket Sort and Average-Case Claims” looks at an algorithm that compares after all and still claims to be linear — and the claim turns out to rest on an assumption about where the keys come from, which is a different kind of promise from anything made so far.

Kept in this browser only, for you. Nothing is sent anywhere and nothing checks it. Carry it to another device.

Feedback and recommendations

Something to change about this lesson? Write it down, tick it off when it is handled, and export the list when you want to act on it. Kept in this browser only: nothing is sent anywhere, and nobody is notified.

No recommendations yet for this lesson.