Sort ten three-digit numbers by their last digit first, then by the middle one, then by the first. It seems backwards. Watch the third pass finish the job.
Sorting numbers digit by digit
Ten three-digit numbers sorted in three passes
static void sort_by_digit(int *a, int *tmp, int n, int exp){ int count[10] = { 0 }; for (int i = 0; i < n; i++) count[(a[i] / exp) % 10]++; for (int d = 1; d < 10; d++) count[d] += count[d - 1]; for (int i = n - 1; i >= 0; i--) { int d = (a[i] / exp) % 10; count[d]--; tmp[count[d]] = a[i]; } for (int i = 0; i < n; i++) a[i] = tmp[i];} int main(void){ int a[N] = { 512, 307, 845, 120, 693, 251, 718, 406, 934, 165 }; int tmp[N]; for (int exp = 1; exp <= 100; exp *= 10) { sort_by_digit(a, tmp, N, exp); for (int i = 0; i < N; i++) printf("%d ", a[i]); printf("\n"); } return 0;main has started: a and tmp have room for ten ints each.
- pass
- –
- dealt
- 0
Output
Describe this picture
Two pictures stacked, with the code beside them, the line about to run marked. At the top, the ten cells of int a[10]: 512 307 845 120 693 251 718 406 934 165. Below, tmp drawn as ten bins, numbered 0 to 9, one per digit; each bin is the run of cells of tmp the running totals give that digit. The clip takes about 35 seconds, in three passes. In the first pass, by the units digit, the numbers are dealt from the right: the marker i starts at 165, its cell is tinted, a line over the bins reads “(165 / 1) % 10 = 5”, bin 5 lights and 165 flies into it. The cells already dealt grey. When all ten are in their bins, a takes them back in bin order: 120 251 512 693 934 845 165 406 307 718. The second pass, by the tens digit, gives 406 307 512 718 120 934 845 251 165 693. The third, by the hundreds digit, puts 120 and 165 in bin 1 in that order, and a ends sorted: 120 165 251 307 406 512 693 718 845 934. Under the picture are one sentence on the current step and the readouts “pass” and “dealt”. The transport plays and pauses, steps one beat back or forward, and scrubs; a paused step is kept in the page address as radix-sort.step.
Last digit first
Each pass is a Counting sort on one decimal digit: count the digits, take running totals, place the numbers from the right into tmp, copy back. Because counting sort places from the right, each pass is stable: numbers with the same digit keep the order they came in.
That is why the last digit goes first. After the units pass, the numbers are in order of their units. The tens pass sorts them by tens, and among numbers with the same tens digit it keeps the units order, so the numbers are now in order of their last two digits. The hundreds pass does the same for all three digits. Each pass must be stable, or it would throw away the order the earlier passes built.
The code and its cost
static void sort_by_digit(int *a, int *tmp, int n, int exp)
{
int count[10] = { 0 };
for (int i = 0; i < n; i++)
count[(a[i] / exp) % 10]++;
for (int d = 1; d < 10; d++)
count[d] += count[d - 1];
for (int i = n - 1; i >= 0; i--) {
int d = (a[i] / exp) % 10;
count[d]--;
tmp[count[d]] = a[i];
}
for (int i = 0; i < n; i++)
a[i] = tmp[i];
}
(a[i] / exp) % 10 is the digit: with exp = 1 the units, 10 the tens, 100 the hundreds. main calls sort_by_digit for exp = 1, 10 and 100, and prints a after each pass:
120 251 512 693 934 845 165 406 307 718
406 307 512 718 120 934 845 251 165 693
120 165 251 307 406 512 693 718 845 934
Look at 120 and 165 in the last pass. Both have hundreds digit 1, so the hundreds pass cannot tell them apart; it keeps them in the order the tens pass left them, where 120 (tens digit 2) came before 165 (tens digit 6).
A pass costs n steps for each of its loops over the numbers and b for each loop over the digits. With d digits in base b, the whole sort is O(d(n + b)). For a fixed number of digits that is linear in n, and no key is ever compared with another.
Bases and the other direction
The base need not be 10. Base 256 sorts unsigned 32-bit integers in 4 passes (signed ones need their sign bit flipped first, so that negatives come before positives), one byte each, with 256 counters a pass; the digit is (x >> (8 * p)) & 0xFF. A larger base means fewer passes but more counters.
The most-significant-digit variant goes the other way: deal by the first digit into bins, then sort each bin separately and recursively by the next digit. It can stop as soon as a bin holds one item, and it suits strings, whose first characters usually decide the order.
Worked example
An unstable last pass. Suppose the hundreds pass placed numbers with equal digits in any order. 120 and 165 both have hundreds digit 1, so 165 could land before 120, and the result would be wrong even though every number is in the right bin.
A million 32-bit keys. In base 256: 4 passes × (10⁶ + 256) steps ≈ 4 × 10⁶. A good comparison sort needs about n log₂ n ≈ 2 × 10⁷ comparisons.
Two checks. How many passes for six-digit numbers in base 10? (6.) In base 1,000? (2.)
Where it’s used
Large arrays of integer keys, where a few byte passes beat comparisons.
Strings of a fixed length, such as codes and dates written as digits, one character per pass from the right.
Graphics, which sorts thousands of objects by their depth every frame.
Reference card
| Code or idea | Meaning |
|---|---|
(a[i] / exp) % 10 | the digit for exp = 1, 10, 100, … |
| one pass | a stable counting sort by one digit |
| order of passes | least significant digit first |
| why stable | ties keep the order the earlier passes built |
| cost | O(d(n + b)): d digits, base b |
| base 256 | 32-bit keys in 4 byte passes |
| MSD first | bins, then each bin recursively; suits strings |
Platform: C11, gcc on x86-64 Linux: int 4 bytes, pointers 8, little-endian. Addresses in the pictures are short stand-ins, by one rule for the whole course; sizes are the real ones.