Radix Sort

Context: FIT2004_MOC · the fix for Counting Sort’s blow-up — stop treating the key as one huge number, treat it as narrow columns · still non-comparison, so the floor in Sorting Problem does not bind

Quick Revision

  • 🎯 Objective: run a stable Counting Sort once per digit column, least-significant first, beating Merge Sort’s for fixed-width keys.
  • 📦 Core Components: items columns | base (digits · bits · letters ) | stable subsort per column.
  • ⚡ Key Constraint: the subsort must be stable — an unstable column pass destroys all order won by the previous passes, and the output is wrong, not merely unsorted.

📝 How It Works

1. The Reframing

  • Key as a grid ➔ lay the items as rows, their digits as columns; the rightmost column is least significant, the leftmost most significant.
  • Why collapses ➔ each column holds only base- symbols, so every subsort sees (decimal), (binary), (alphabet) — never the or that ruined plain Counting Sort.
  • is a length, not a count ⟹ raising the base shrinks but grows ; the trade is the design decision the exam asks about.
  • Lecture notation map — memorise both ➔ this note’s (columns) is the lecture’s ; this note’s (base) is the lecture’s ; the lecture reserves for the largest item. In lecture symbols: , time , auxiliary .
  • Digit extraction is arithmetic, not string slicing ➔ column (counting from at the least significant end) of key is per digit, no conversion to text.

2. Least-Significant-Digit Order + Stability

  • Pass order ➔ sort on column (rightmost), then , … , then column ⟹ after pass the list is correctly sorted on the last digits.
  • Stability carries the earlier work ➔ when column ties, a stable subsort preserves the previous pass’s order, which is exactly the ordering on the lower-significance digits ⟹ the invariant is maintained.
  • Unstable ⟹ catastrophe ➔ with before from pass 2, an unstable hundreds pass may emit before ; both have hundreds digit , so nothing later repairs it.

3. Choosing the Base — Where Linearity Comes From

  • The base is a free design parameter ➔ nothing forces ; you pick , and that choice alone decides whether the sort is linear.
  • Raise up to ➔ the per-pass cost is , so any leaves it at ⟹ total . Choosing is the standard move because it buys the smallest at no asymptotic cost.
  • Never let ➔ the count array of size then dominates the items, the per-pass cost becomes , and the sort is no longer linear in — it is linear in the base you chose.
  • The linearity theorem ➔ with , ; so if the largest item satisfies for a constant , then and radix sort runs in .
  • Where it stops being linear super-polynomial in (, ) makes grow with — the distinct-keys argument of the “When It Flips” note, reached from the base side.
  • is the asymptotic rule, NOT the constant-factor optimum [D] ➔ for integers of bits, and the operation count is

which is minimised well below : at the minimum sits near , so the practical choice is — the nearest power of two whose exponent divides the word width, giving exactly passes.

  • Powers of two win twice ➔ a base of makes digit extraction a shift and mask instead of a division, and choosing avoids a ragged final column; measured timings degrade sharply for and because and do not divide .

4. Ragged Keys

  • Unequal lengths break the column gridbanhammer and kappa have no common .
  • Pad to a common width ➔ insert a filler symbol that sorts below every real symbol; numbers pad with leading zeros (right-aligned), strings pad with spaces.
  • Alignment decides the order semantics ➔ right-aligned padding gives numeric order; left-aligned padding gives lexicographic order — choose deliberately, they differ.

5. Optimising for Strings — the Algorithm (applied Problem 4 — [D, HD])

  • Applied-sheet notation number of strings · length of the longest string · total characters. The optimal bound is in that — do not read it as this note’s item count.
  • Naive padding is ➔ every string is scanned times regardless of its own length ⟹ pathological when lengths are skewed: strings of length plus one string of length costs operations, against actual characters.
  • The fix — never look at a string before it has a character to contribute ➔ a string of length only matters in the final iterations of an LSD sweep, so exclude it from every earlier pass.
  • Step 1 — counting-sort by length, ASCENDING ➔ key , range . Ascending is load-bearing: shorter strings must precede longer ones that share a prefix (cat before cats), which is exactly alphabetical order.
  • Step 2 — sweep columns down to with a live pointer ➔ maintain such that are the strings of length ; on each decrement of , decrement while ⟹ strings enter the working window exactly when they acquire a character at position , and never leave.
  • Step 3 — run the stable counting subsort on only ➔ base , so no pass ever touches a non-existent character.
  • Why it totals ➔ string participates in exactly passes ⟹ the work is , plus the length sort ⟹ optimal, since every character must be read at least once.
  • Alignment decides the semantics, not the speed ➔ this scheme is left-aligned/lexicographic (position is the first character); right-aligned padding is the numeric reading and is the wrong model for words ➔ §4.

⚙️ Core Implementation

🔹 LSD radix sort over base

🔹 radix sort for variable-length strings

⚖️ Core Decision Matrix

AlgorithmTimeAuxiliary spaceStableSelection rule
Counting Sortengineeredkeys are integers with
Radix Sortrequiredkeys decompose into narrow columns, small/fixed
Merge Sortyeskeys not integer-decomposable, or grows with
Quick Sort avg, worstnoin-place matters more than the guarantee

When It Flips: radix beats Merge Sort while . Fixed-width keys (-bit ints, decimal digits) ⟹ is a constant. Distinct keys force , so the advantage shrinks exactly when the keys become long — and raising base buys down at time and space.

📊 Exam Execution Trace & Applied Exercises

Manual Execution Trace

[200, 151, 291, 981, 369, 421, 671], base , , LSD first. Digit sorted on is bold.

PassColumnDigits readList after the pass
0 (Init)200 151 291 981 369 421 671
1units200 151 291 981 421 671 369
2tens200 421 151 369 671 981 291
3hundreds151 200 291 369 421 671 981

Stability check on pass 3: and both have hundreds digit ; pass 2 left before , and the stable subsort keeps it ⟹ correct. An unstable pass could emit 151 291 200 … — wrong final answer.

Applied Exercise

Problem: Derive radix sort’s time and auxiliary space from Counting Sort’s , then specialise to base- integers.

Final Extracted Output: time, total space, auxiliary — auxiliary carries no , because the same count/position/output arrays are reused every pass rather than allocated per column.

⚠️ Common Mistakes

  • 💡 Multiplying into the auxiliary space ➔ the subsort arrays are reused across passes ⟹ auxiliary is ; only the input carries the factor.
  • 💡 Reusing the two meanings of ➔ in Counting Sort is the maximum key; in radix sort is the base (number of distinct symbols per column). Name which one you mean before quoting a bound.
  • 💡 Calling radix sort with no conditions ➔ true only once you state that and are capped; for distinct keys , which recovers .
  • 💡 Sizing the count array from max() ➔ that is Counting Sort’s rule; radix sizes it from the base. You still scan for max — but only to compute the number of columns . Lecturer-flagged as the recurring code-review error.

🧠 Active Recall