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 previous passes, and the output is wrong, not merely unsorted.

📝 How It Works

1. The Reframing

  • Key as a grid ➔ items are rows, digits are columns; rightmost = least 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 ; that trade is the exam’s design question.
  • 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 (from at the least significant end) of key is ⟹ per digit.

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 the ordering on the lower-significance digits ⟹ the invariant extends.
  • Unstable ⟹ catastrophe ➔ with before from pass 2, an unstable hundreds pass may emit first; both share hundreds digit , so nothing later repairs it.

3. Choosing the Base — Where Linearity Comes From

  • The base is a free design parameter ➔ nothing forces ; that choice alone decides whether the sort is linear.
  • Raise up to ➔ per-pass cost is , so any leaves it at ⟹ total ; buys the smallest at no asymptotic cost.
  • Never let ➔ the size- count array then dominates the items and the sort is linear in the base, not in .
  • The linearity theorem ➔ with , ; so if the largest item satisfies for constant , then and radix sort is :
  • Where it stops being linear ➔ super-polynomial in (, ) makes grow with — the distinct-keys argument reached from the base side.
  • is the asymptotic rule, NOT the constant-factor optimum [D] ➔ for integers of bits, and the operation count 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 ➔ base makes digit extraction a shift and mask instead of a division, and avoids a ragged final column; timings degrade sharply for and because and do not divide .

4. Ragged Keys

  • Unequal lengths break the column grid ➔ banhammer and kappa have no common .
  • Pad to a common width ➔ insert a filler that sorts below every real symbol; numbers pad with leading zeros (right-aligned), strings with spaces.
  • Alignment decides the order semantics ➔ right-aligned padding gives numeric order, left-aligned gives lexicographic — 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 · = total characters. The optimal bound is in that .
  • Naive padding is ➔ every string is scanned times regardless of its own length ⟹ strings of length plus one 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.
  • 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) — exactly alphabetical order.
  • Step 2 — sweep columns down to with a live pointer ➔ maintain so that are the strings of length ; on each decrement of , decrement while ⟹ strings enter the 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 touches a non-existent character.
  • Why it totals ➔ string participates in exactly passes ⟹ work , plus ⟹ optimal, since every character must be read once.
  • This scheme is left-aligned/lexicographic ➔ position is the first character; right-aligned padding is the numeric reading and 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 , degenerating back to — so the advantage shrinks exactly when keys become long; raising base buys down at time and space, and stops paying once approaches .

📊 Exam Execution Trace & Applied Exercises

Manual Execution Trace

[200, 151, 291, 981, 369, 421, 671], base , , LSD first:

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. At : , .

⚠️ 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; here is the base. Name which one you mean before quoting a bound.
  • 💡 Calling radix sort with no conditions ➔ true only once you state that and are capped; distinct keys force , recovering .
  • 💡 Sizing the count array from max() ➔ that is Counting Sort’s rule; radix sizes it from the base and uses max only for the column count . Lecturer-flagged as the recurring code-review error.

🧠 Active Recall