Algorithm Search⌘K

Proofs: American flag sort

These are the human proofs of American flag sort, at full length.

American flag sort sorts strings in lexicographic order full

This proof shows: correctness, termination.

Let c(s,d)=s[d]+1c(s, d) = s[d] + 1 for d<∣s∣d < |s| and c(s,d)=0c(s, d) = 0 otherwise. A task (lo,hi,d)(lo, hi, d) is valid if the strings of A[lo…hi−1]A[lo \ldots hi - 1] have length at least dd and agree on their first dd characters.

Fact 1. Let ss and tt agree on their first dd characters, with ∣s∣,∣t∣≥d|s|, |t| \ge d. If c(s,d)<c(t,d)c(s, d) < c(t, d) then s<ts < t. If c(s,d)=c(t,d)=0c(s, d) = c(t, d) = 0 then s=ts = t. If c(s,d)=c(t,d)≥1c(s, d) = c(t, d) \ge 1 then they agree on d+1d + 1 characters. (This is Fact 1 of MSD radix sort, with the same proof.)

Lemma (one step). Let a task (lo,hi,d)(lo, hi, d) with m=hi−lo≥2m = hi - lo \ge 2 be popped. After lines L5 to L11, the range A[lo…hi−1]A[lo \ldots hi - 1] is a permutation of its old content, and for each rr the block A[start[r]…end[r]−1]A[start[r] \ldots end[r] - 1] holds exactly the strings with c(⋅,d)=rc(\cdot, d) = r.

Proof. The blocks are disjoint and cover [lo,hi)[lo, hi), in the order of rr, because start[r+1]=start[r]+C[r]=end[r]start[r + 1] = start[r] + C[r] = end[r] and end[R]=hiend[R] = hi. We show that the following holds before and after each execution of lines L9 to L11:

(P1) for each bb, start[b]≤next[b]≤end[b]start[b] \le next[b] \le end[b], and A[start[b]…next[b]−1]A[start[b] \ldots next[b] - 1] holds strings of bucket bb; (P2) for each bb, the number of strings of bucket bb at positions outside [start[b],next[b])[start[b], next[b]) is end[b]−next[b]end[b] - next[b], and these positions are all in blocks b′b' at positions in [next[b′],end[b′])[next[b'], end[b']); (P3) the range is a permutation of its old content.

At the start next[b]=start[b]next[b] = start[b], so (P1) holds; there are C[b]=end[b]−start[b]C[b] = end[b] - start[b] strings of bucket bb, so (P2) holds; and (P3) holds.

In the scan of bucket rr, let i=next[r]<end[r]i = next[r] < end[r] and b=c(A[i],d)b = c(A[i], d). If b=rb = r, line L10 extends block rr by a string of bucket rr, and (P1) to (P3) still hold. If b≠rb \ne r, the string at ii is an unplaced string of bucket bb, so by (P2) end[b]−next[b]≥1end[b] - next[b] \ge 1 and next[b]<end[b]next[b] < end[b]. Line L11 swaps it into position next[b]next[b] and then increases next[b]next[b]. Block bb grows by a string of bucket bb, so (P1) holds for bb; the string that moved to ii was at an unplaced position, and ii is still unplaced in block rr, so (P1) holds for rr and (P2) holds for every bucket. A swap keeps (P3).

Each execution of lines L9 to L11 increases ∑bnext[b]\sum_b next[b] by 1, and this sum is at most ∑bend[b]\sum_b end[b], so the loops end, after at most mm executions. When the scan of bucket rr ends, next[r]=end[r]next[r] = end[r], and next[r]next[r] never decreases. So at the end next[b]=end[b]next[b] = end[b] for all bb, and by (P1) each block holds only strings of its bucket. □\square

Correctness. Call the state good if the tasks on SS have disjoint ranges and are valid, every pair of indices i<ji < j that are not in the same task has A[i]≤A[j]A[i] \le A[j], and AA holds the input strings. The first task (0,n,0)(0, n, 0) makes the state good. When a task with at most one string is popped, nothing changes and the state stays good. When a task with m≥2m \ge 2 strings is popped, the lemma gives blocks in the order of c(⋅,d)c(\cdot, d).

  • Two strings in different blocks are in order by Fact 1.
  • Two strings in block 0 are equal by Fact 1.
  • Each block r≥1r \ge 1 with at least two strings becomes a task, valid at d+1d + 1 by Fact 1. A block with one string has no partner.
  • Pairs with one index outside [lo,hi)[lo, hi) keep their relation, because the range holds the same strings as before.

So the state stays good.

Termination. Let LL be the largest length of a string. A valid task with at least one string has d≤Ld \le L. Give a task the weight (hi−lo)(L+1−d)(hi - lo)(L + 1 - d), as in the proof for MSD radix sort. A pop of a task with m≥2m \ge 2 strings replaces its weight m(L+1−d)m(L + 1 - d) by children of total weight at most m(L−d)m(L - d), so the total weight drops by at least m≥2m \ge 2, and other pops do not increase it. So the number of pops of tasks with two or more strings is at most n(L+1)/2n(L + 1)/2. Line L12 pushes only tasks with at least two strings, so every pop after the first is of such a task, and the loop ends.

Result. When SS is empty, every pair i<ji < j has A[i]≤A[j]A[i] \le A[j], and AA holds the input strings, so AA is sorted. ■\blacksquare

Back to American flag sort