Counting Sort

Context: FIT2004_MOC Β· the first non-comparison sort β€” tally keys in an index-addressed array instead of comparing them Β· the stable subroutine Radix Sort is built from Β· contrast the comparison floor in Sorting Problem

Quick Revision

  • 🎯 Objective: use each key as an array index into a frequency table βž” sort in with zero comparisons, beating the comparison lower bound.
  • πŸ“¦ Core Components: max scan | count array size | tally via indexing | rebuild .
  • ⚑ Key Constraint: is the key range, not the item count βž” one huge value inflates and both time and space; the sort is only a win when .

πŸ“ How It Works

1. Why It Escapes the Comparison Bound

  • No comparisons βž” the key is the address β€” count[key] += 1 never asks ”?” ⟹ the floor for comparison sorts simply does not apply.
  • Precondition βž” keys must be non-negative integers in a bounded range that index an array in ; arbitrary orderable objects do not qualify.

2. The Four Phases

  • Phase 1 β€” find max βž” one linear scan gives ⟹ ; you cannot allocate before you know the range.
  • Phase 2 β€” allocate count βž” zero-filled array of length , position holds ⟹ .
  • Phase 3 β€” tally βž” for each item, count[item] += 1 ⟹ , because array access is ; that is the whole trick.
  • Phase 4 β€” rebuild βž” walk count leftβ†’right emitting value exactly times ⟹ β€” cells visited, items written.

3. Stability Is Not Free

  • Naive version is UNSTABLE βž” storing only a frequency discards item identity; the emitted items are freshly manufactured copies of the key, so the payload attached to equal keys is lost/reordered.
  • Fix A β€” chained buckets βž” store the items in a list per slot (the separate-chaining shape) and append in input order ⟹ stable.
  • Space of Fix A is , NOT βž” the buckets partition the input, so all buckets together hold exactly items β€” the slots and the payloads add, never multiply.
  • Fix B β€” position (prefix-sum) array βž” turn counts into starting offsets, then place items in a single input-order pass ⟹ stable with a flat array, no lists βž” Β§4.
  • Which variant does the question want? βž” unstated ⟹ buckets (simpler, and correct across the repeated passes of Radix Sort); a question naming a count array and a position array is asking for Fix B β€” that pairing is the non-bucket variant’s signature.
  • Bucket drain must be per item βž” concatenate with extend() (amortised ), never pop(0), which shifts the whole list at per item and silently turns the rebuild into ; a circular queue / deque restores front removal if FIFO order must be popped rather than copied.

4. Stable Counting Sort via a Position Array

  • Build count βž” count[key] += 1 over the input, as before.
  • Build position βž” position[first] = 1, then βž” a prefix sum: each key learns where its block starts.
  • Construct output βž” scan the input in order; for each write output[position[key]] = (key,val) then position[key] += 1 ⟹ earlier-arriving equal keys land in earlier slots ⟹ stable.
  • Cost unchanged βž” still time, space β€” stability costs an extra array, not an extra factor.

5. Negative and Non-Zero-Based Keys

  • The count array is indexed from βž” a negative key has no slot, so a raw count[key] fails before it sorts.
  • Offset mapping βž” scan for min as well as max, then store every key at index ⟹ every index becomes valid (, ) and the range is .
  • Undo the shift on rebuild βž” emit , never the index itself β€” forgetting this returns a correctly ordered list of the wrong values.
  • The bound is driven by the RANGE, not the maximum βž” with ; a tight cluster of huge values () is cheap, while is ruinous.

βš™οΈ Core Implementation

πŸ”Ή Basic counting sort (unstable, keys only)

πŸ”Ή Stable counting sort (position array, key–value pairs)

βš–οΈ Core Decision Matrix

VariantTrigger conditionProCon / complexity boundMemory impact
Frequency onlykeys carry no payloadsimplest; rebuild is two loopsunstable β€” identity discarded auxiliary
Chained bucketspayloads present, lists acceptablestable; conceptually easypointer chasing; per-node overhead auxiliary
Position prefix-sumpayloads present, Radix Sort subroutinestable, flat arrays, cache-friendlytwo extra passes, -index bookkeeping auxiliary
Merge Sort (comparison) unbounded or keys not integersworks on any orderable type

When It Flips: counting sort wins while . Sorting lowercase letters () is ; sorting numbers where one is costs β€” worse than Merge Sort. Once dominates, either switch to a comparison sort or decompose the key into digits βž” Radix Sort.

πŸ“Š Exam Execution Trace & Applied Exercises

Manual Execution Trace

Stable counting sort on (3,a) (1,p) (3,c) (7,f) (5,g) (3,b) (7,d) (8,w), keys . Counts: ⟹ prefix positions .

StepItem readSlot written afterOutput so far
0 (Init)β€”β€”β€”[_ _ _ _ _ _ _ _]
12[_ a _ _ _ _ _ _]
21[p a _ _ _ _ _ _]
33[p a c _ _ _ _ _]
46[p a c _ _ f _ _]
55[p a c _ g f _ _]
64[p a c b g f _ _]
77[p a c b g f d _]
88[p a c b g f d w]

Final: β€” the key- payloads keep their input order ⟹ stable.

Applied Exercise

Problem: Derive total time and auxiliary space, then decide whether counting sort beats Merge Sort on with input .

Final Extracted Output: time, / auxiliary; here so counting sort loses badly β€” the fix is to sort digit-by-digit with βž” Radix Sort.

⚠️ Common Mistakes

  • πŸ’‘ Quoting unconditionally βž” the bound is ; may only be dropped after you state that the key range is capped (alphabet , digits ).
  • πŸ’‘ Claiming bucket space is βž” lecturer-flagged as the very common misconception: buckets partition the input, so the total payload is ⟹ .
  • πŸ’‘ Assuming counting sort is stable by default βž” it is not; stability must be engineered (buckets or prefix-sum positions), and Radix Sort silently breaks without it.
  • πŸ’‘ Draining buckets with pop(0) βž” per removal from shifting ⟹ the rebuild becomes and the whole linear claim collapses; use extend() or a circular queue.
  • πŸ’‘ Carrying the max() scan into Radix Sort βž” counting sort needs max to size the count array; radix sizes it from the base and needs max only to compute the column count. Lecturer-flagged as a recurring code-review error.

🧠 Active Recall