Linear-time sorting
Comparison sorts can't beat O(n log n). Sorts that use the keys themselves as positions can, when the keys are small whole numbers or short, fixed-length codes.
If the key is a small number, it can be an index
Merge sort & quicksort ended with a limit: a sort that learns about items only by comparing them needs about n log₂ n comparisons. The way around it is not to compare at all.
Suppose you sort 10,000 exam scores, each a whole number from 0 to 100. You don't need to compare scores with each other. Keep 101 counters, one per possible score, read each score once and add one to its counter, then write the scores back out in counter order. Each score was touched twice, whatever the order it arrived in.
Counting sort tallies each value, then writes the values back in order
Counting sort does exactly that. For values from 0 to k: count how many times each value appears, then walk the counts from 0 to k and write each value out as many times as it was counted.
The tally reads the n items once, and writing out walks all k + 1 counters: O(n + k) time and O(k) extra memory. When k is no bigger than about n, that is O(n).
The rule
Counting sort runs in O(n + k) for whole-number keys from 0 to k. It beats comparison sorts only when k is small next to n.
A wide range of values ruins it
The k in O(n + k) is the whole range of possible values, not how many values you have. Sorting 1,000 user IDs that run up to a billion would mean a billion counters, almost all of them zero. Counting sort suits small, dense ranges: ages, exam scores, letters, days of the month.
It also needs whole-number keys, or keys that map to them. Floating-point numbers or arbitrary strings have no small range to count over.
To sort records, turn the counts into positions
Writing values back from the counts works for plain numbers, but usually the numbers are keys of something bigger: people sorted by age, orders by day of the month. Then each record has to be moved, and equal keys should keep their order. The counts tell you where everything goes:
- Count
Tally each key, as before.
- Turn counts into starting positions
Walk the keys in order, keeping a running total. Key 0's records start at position 0; each later key starts where the previous key's records end.
- Place the records
Read the input from front to back. Put each record at its key's next free position, then move that position on by one.
Because records are placed in the order they were read, records with equal keys keep their original order: this version of counting sort is stable. That property is what the next sort is built on.
Radix sort sorts one digit at a time, starting from the last
Numbers up to a billion are too wide for counting sort, but each digit has only 10 values. Radix sort sorts by the ones digit, then by the tens digit, then the hundreds, each pass a stable distribution into 10 buckets.
Why does it work? Each pass is stable, so numbers with the same digit in this pass keep the order the earlier passes gave them, which is their order by the lower digits. After the last pass, numbers are ordered by their highest digit, ties broken by the next, and so on: fully sorted.
With d digits and base 10, radix sort makes d passes of O(n + 10): O(d × n) in all. For fixed-width keys such as 32-bit integers, phone numbers or zip codes, d is a constant and the sort is linear. Real implementations use a larger base, often 256 (one byte at a time), to cut the number of passes.
Counting replaces sorting when the range can be capped
The h-index can't be larger than the number of papers, n, so any citation count above n can be treated as n. That caps the range at 0 … n, which makes counting sort's tally possible: count papers per citation count, then walk down from n adding up how many papers have at least h citations. O(n), no sorting.
Bucket sort relies on values being spread out
Bucket sort splits a known range into n equal buckets, drops each value into its bucket, sorts each bucket with insertion sort, and reads the buckets in order. If the values are spread evenly across the range, each bucket holds about one value and the whole sort is O(n) on average.
If the values cluster, most of them land in a few buckets and the sorting inside those buckets dominates. Bucket sort is a bet on the input's distribution, not a guarantee.
Sort | Time | Needs | Stable |
|---|---|---|---|
Counting | O(n + k) | whole-number keys in a small range 0 … k | Yes, with positions |
Radix | O(d × n) | fixed-length keys, d digits | Yes |
Bucket | O(n) on average | values spread evenly over a known range | Yes, with a stable inner sort |
Merge sort, quicksort | O(n log n) | only comparisons | Merge sort only |
4 questions
Remember this
- Sorts that use keys as positions instead of comparing them can beat O(n log n).
- Counting sort is O(n + k) for whole numbers from 0 to k: fast when k is small, hopeless when the range is huge.
- To sort records stably, turn the counts into starting positions with a running total and place records in input order.
- Radix sort makes one stable pass per digit, ones first: O(d × n) for d-digit keys.
- Bucket sort is O(n) on average only when values are spread evenly over a known range.
Now solve some yourself
Only this topic 0 of 7 solved
- EasyHow many people stand out of order?
- EasySort up to a million numbers
- EasySort by another list's order
- EasyEvery number from 1 to n that is missing
- EasyHow many values are smaller than each one, by counting
- MediumLargest gap between neighbors once sorted, in O(n)
- HardThe smallest missing positive, O(n) and O(1) space
Mixed with topics you have practiced 0 unlocked
2 more once you have practiced Hash tables & maps. Play one of its examples to the end and solve one of its problems to unlock them.
Problems open on their own sites (LeetCode, Codeforces, CodeChef, AtCoder, CSES) in a new tab. Tick Solved when you get it accepted there; your ticks decide which mixed problems open next.
Next