3.6.6 · D4Sorting & Searching

Exercises — Counting sort — O(n + k), integer keys only

2,135 words10 min readBack to topic

This is the practice page for Counting Sort. Work each problem before opening its solution. The problems climb five levels — from "just recognise it" up to "invent and combine". Each level ends with the single trap that catches most people there.

The two figures below show the two mechanical moves you'll repeat all page: the prefix-sum turning counts into boundaries, and one right-to-left placement.

Figure — Counting sort — O(n + k), integer keys only
Figure — Counting sort — O(n + k), integer keys only

Level 1 — Recognition

Recall Solution 1.1

Here (number of items) and (largest possible key). Counting sort is operations — effectively linear. Since is smaller than , the range is tiny relative to the data. Yes, excellent fit. The count array needs size . Trivial memory.

Recall Solution 1.2
  • (a) , : great.
  • (b) , : terrible — you'd allocate ~4.3 billion cells to sort 50 numbers. Time and space blow up to .
  • (c) , : great. Rule: counting sort wins only when . Case (b) has .

Level 2 — Application

Recall Solution 2.1

Tally (count[0..4]): values seen — one , three 's, one , two 's. Prefix sum (each cell = itself + previous): . Read as "# of keys ": is , is , is , is (all of them ✓). Place, scanning right→left ( = key, do count[v]-=1 then out[count[v]]=v):

  • : count[1]=4→3, out[3]=1
  • : count[4]=7→6, out[6]=4
  • : count[0]=1→0, out[0]=0
  • : count[1]=3→2, out[2]=1
  • : count[3]=5→4, out[4]=3
  • : count[1]=2→1, out[1]=1
  • : count[4]=6→5, out[5]=4 Result: . ✓
Recall Solution 2.2

Keys aren't in : the minimum is . Offset every key by , so index . Shifted keys: . Range is , so , count size . Tally: . Prefix: . Sort the shifted keys → . Un-shift (subtract ): . ✓


Level 3 — Analysis

Recall Solution 3.1

Tally: one key-, two key-count=[1,2]. Prefix: count=[1,3]. Now scan left→right (count[v]-=1 then out[count[v]]=x):

  • : count[1]=3→2, out[2]=(1,'a')
  • : count[1]=2→1, out[1]=(1,'b')
  • : count[0]=1→0, out[0]=(0,'c') Result: . The keys are sorted () — so the bug is invisible on plain numbers. But among the equal 1's, the original order was a then b; the output has b then a. Stability is destroyed. Right-to-left placement would have kept a before b.
Recall Solution 3.2

Cost — dominated entirely by . It is not linear in ; you allocate ~ cells to sort items. Here , so counting sort degrades badly (compare to for a comparison sort — over 700× less work). Use a comparison sort, or Radix Sort which breaks each large key into small-radix digits and runs counting sort per digit, keeping each pass's small.


Level 4 — Synthesis

Recall Solution 4.1

Write each as two digits: . Pass 1 — ones digit (keys ). Stable counting sort by ones: Tally at digits : count[1]=1, [2]=1, [3]=1, [5]=2. Ordering by ones digit (stable): . (The two 5's, then , keep their input order — stability.) Pass 2 — tens digit (of , tens digits ). Stable counting sort by tens: . Final: . ✓ Fully sorted — and stability in each pass is what let earlier-digit order survive.

Recall Solution 4.2

Use key(x) to read the integer, but write the whole record: out[count[v]] = x. Tally on keys : count=[1,1,2]. Prefix: count=[1,2,4]. Place right→left:

  • : count[1]=2→1, out[1]=(1,"w")
  • : count[2]=4→3, out[3]=(2,"z")
  • : count[0]=1→0, out[0]=(0,"y")
  • : count[2]=3→2, out[2]=(2,"x") Result: . Among key- records, (input-first) precedes stable copy confirmed. See Stability in Sorting.

Level 5 — Mastery

Recall Solution 5.1

Let = count of value , and . After prefix sum, count[v] . During placement, the first time we meet a we write at count[v]-1 ; each subsequent decrements, so we write at indices exactly slots — the contiguous block . No overlap: the next value starts at , which is exactly one past 's last slot. So block of ends at and block of begins at . Adjacent, never overlapping. Every index is covered once. ∎ Check on : counts . . Blocks: , , . Prefix count=[1,1,3,4]. Sorted output — index holds , indices hold 's, index holds . Matches the blocks exactly. ✓

Recall Solution 5.2

The bound applies only to comparison-based sorts — algorithms whose only way to learn about the data is asking "is ?". A binary decision tree of comparisons must have at least leaves (one per possible ordering), so its depth is . Counting sort never asks that question. It uses the key itself as an array index — reading "value → bucket " is an random-access operation, not a comparison. The decision-tree model simply doesn't describe it, so its lower bound doesn't apply. The assumption exploited: keys are integers in a small known range that can serve directly as array indices. Remove that (arbitrary floats, huge , strings without a small-integer mapping) and you're forced back to comparisons — and the wall returns. See Time Complexity / Big-O.


Flashcards

The deciding inequality for "counting sort is a good fit"?
— the key range must be small relative to the number of items; if it degrades to .
Why does left-to-right placement still produce sorted numbers but is still buggy?
Keys sort correctly, but equal records get their relative order reversed — stability is lost, invisible on bare integers but fatal inside radix sort.
Block of value in the output occupies which index range?
where and is 's count.
Why is there no contradiction with the bound?
That bound is only for comparison sorts; counting sort uses keys as array indices, never comparing, so the comparison decision-tree model doesn't apply.
Must-do between two radix-sort digit passes?
Zero (or reallocate) the count array — reusing the decremented/prefixed one from the previous pass corrupts the next.