If every score is a whole number from 0 to 9, count how many marks got each score, and every mark knows where it goes. Watch: no two marks are ever compared.
Sorting by counting
Twelve marks from 0 to 9
struct mark { int score; char id;}; void counting_sort(const struct mark *in, struct mark *out, int n){ int count[K] = { 0 }; for (int i = 0; i < n; i++) count[in[i].score]++; for (int m = 1; m < K; m++) count[m] += count[m - 1]; for (int i = n - 1; i >= 0; i--) { count[in[i].score]--; out[count[in[i].score]] = in[i]; }}main has started: scores, in and out, nothing written yet.
- pass
- –
- placed
- 0
- comparisons
- 0
Output
Describe this picture
Three rows of cells, with the code beside them, the line about to run marked. The top row is in, twelve marks, each a score and a letter: 7A 3B 9C 3D 5E 0F 7G 8H 3I 5J 9K 7L. The middle row is count, ten counters numbered 0 to 9, one per score. The bottom row is out, twelve cells shown as question marks until something is written there. The clip takes about 35 seconds. First each mark in turn, marked i, is tinted, and the counter for its score goes up by one: the counters end at 1 0 0 3 0 2 0 3 1 2. Then a marker m moves along the counters, and each adds in the one before it, giving the running totals 1 1 1 4 4 6 6 9 10 12. Last, the marks are placed from the right: 7L first. Its counter, count[7], goes down from 9 to 8, and 7L lands in out[8]; the mark greys in the top row. Then 9K lands in out[11], 5J in out[5], and so on, until out reads 0F 3B 3D 3I 5E 5J 7A 7G 7L 8H 9C 9K. Under the picture are one sentence on the current step and the readouts “pass”, “placed” and “comparisons”, which stays at 0. The transport plays and pauses, steps one beat back or forward, and scrubs; a paused step is kept in the page address as counting-sort.step.
Count, total, place
Suppose every key is a whole number from 0 to k − 1, here a score from 0 to 9. Then sorting needs no comparisons. Three passes do it:
- Count. For each item, add 1 to the counter of its key.
- Total. Turn the counts into running totals: count[m] becomes the number of items with key m or less.
- Place. The items with key m or less fill out[0] to out[count[m] − 1]. So the last item with key m goes in out[count[m] − 1]. Take the items from the right, and for each, subtract 1 from its counter and write it at that cell.
The keys are used as indices, into count, never compared with each other.
The code and its cost
struct mark {
int score;
char id;
};
void counting_sort(const struct mark *in, struct mark *out, int n)
{
int count[K] = { 0 };
for (int i = 0; i < n; i++)
count[in[i].score]++;
for (int m = 1; m < K; m++)
count[m] += count[m - 1];
for (int i = n - 1; i >= 0; i--) {
count[in[i].score]--;
out[count[in[i].score]] = in[i];
}
}
With K = 10 and the twelve marks above:
| Score | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|---|
| count | 1 | 0 | 0 | 3 | 0 | 2 | 0 | 3 | 1 | 2 |
| running total | 1 | 1 | 1 | 4 | 4 | 6 | 6 | 9 | 10 | 12 |
Placing from the right: 7L goes to cell 9 − 1 = 8, 9K to 12 − 1 = 11, 5J to 6 − 1 = 5, and so on. marks.c prints 0F 3B 3D 3I 5E 5J 7A 7G 7L 8H 9C 9K.
The passes take n, k and n steps: O(n + k) time. Besides the input, it needs the k counters and an output array of n items: O(n + k) memory.
Stable
The three marks with score 3 were B, D, I in the input, and they come out as 3B 3D 3I. Placing from the right is what keeps them so: the rightmost 3, I, is placed first, into the last of the three cells for score 3; then D before it, then B. A sort that keeps equal keys in their input order is stable. That matters when the items carry more than the key, and it is what radix sort relies on (Radix sort).
Faster than any comparison sort
A sort that only compares items must, in effect, find out which of the n! possible orders the input is in (Counting). Each comparison has two outcomes, so at best it halves the orders still possible. Telling n! orders apart therefore takes at least log₂ n! comparisons in the worst case, which is about n log₂ n − 1.44n. No comparison sort beats O(n log n).
Counting sort escapes that bound because it never compares. It reads each key and uses it as an address, which only works because the keys are small integers.
Worked example
The bound for ten items. log₂ 10! = log₂ 3,628,800 ≈ 21.8. So no comparison sort can always sort ten items in fewer than 22 comparisons.
When the keys are too big. 1,000 items with keys up to 10⁹ would need k = 10⁹ counters, a million times more counters than items. Counting sort is the wrong tool here. Radix sort splits each key into small digits and counting-sorts one digit at a time.
Placing from the left. Run the last loop from i = 0 upwards instead, and the first 3 placed, B, takes the last cell for score 3. marks-left.c makes only that change and prints 0F 3I 3D 3B 5J 5E 7L 7G 7A 8H 9K 9C: every group of equal scores comes out reversed. The order of the scores is still right, but the sort is no longer stable.
Two checks. Where does the first 7, A, go? (Cell 6: when A is placed, count[7] has already gone from 9 to 7 for L and G, and goes down once more.) How much memory for k = 256 and n = 10⁶? (256 counters and an output of 10⁶ items.)
Where it’s used
Small keys, such as marks, ages and bytes. Sorting the bytes of a file takes 256 counters.
Inside radix sort, which counting-sorts one digit of the key per pass and needs every pass to be stable.
Reference card
| Code or idea | Meaning |
|---|---|
| keys | whole numbers 0 to k − 1 |
| count | count[key]++ for every item |
| total | count[m] += count[m − 1]: items with key ≤ m |
| place | from the right: count[key]−−, out[count[key]] = item |
| time, memory | O(n + k), O(n + k) |
| stable | yes, when placing from the right |
| comparison sorts | at least log₂ n! ≈ n log₂ n − 1.44n comparisons |
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.