Algorithm Search⌘K

Proofs: Multikey quicksort

These are the human proofs of Multikey quicksort, at full length.

Multikey quicksort sorts strings in lexicographic order full

This proof shows: correctness, termination.

Let c(s,d)=s[d]c(s, d) = s[d] for d<∣s∣d < |s| and c(s,d)=−1c(s, d) = -1 otherwise, where −1-1 is less than every character.

Fact 1. Let ss and tt agree on their first dd characters, with ∣s∣≥d|s| \ge d and ∣t∣≥d|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)=−1c(s, d) = c(t, d) = -1, then s=ts = t. If c(s,d)=c(t,d)≠−1c(s, d) = c(t, d) \ne -1, then ss and tt agree on their first d+1d + 1 characters.

Proof. If c(s,d)=−1<c(t,d)c(s, d) = -1 < c(t, d), then ss has length dd and is a proper prefix of tt, so s<ts < t. If −1<c(s,d)<c(t,d)-1 < c(s, d) < c(t, d), then dd is the first index where they differ, and s[d]<t[d]s[d] < t[d]. The other two cases follow from the definition of cc. □\square

A task (lo,hi,d)(lo, hi, d) is valid if all strings of A[lo…hi−1]A[lo \ldots hi - 1] have length at least dd and agree on their first dd characters. Call the state good if:

(G1) the ranges of the tasks on SS are disjoint, and each task is valid; (G2) for indices i<ji < j that are not in the same task, A[i]≤A[j]A[i] \le A[j]; (G3) AA holds the input strings as a multiset.

Initialization. The task (0,n,0)(0, n, 0) is valid, and there is one task, so (G1) to (G3) hold.

Maintenance. Pop (lo,hi,d)(lo, hi, d). If hi−lo≤1hi - lo \le 1, the task is dropped. Its range has at most one index, which was already in a different task from every other index, so (G2) already covers all its pairs. Otherwise, line L6 permutes the range, so (G3) holds, and it creates three ranges [lo,lt)[lo, lt), [lt,gt)[lt, gt) and [gt,hi)[gt, hi) with c(⋅,d)<vc(\cdot, d) < v, =v= v and >v> v.

  • The two outer ranges hold strings of the old task, so they are valid at depth dd.
  • If v≠−1v \ne -1, the strings of the middle range agree on d+1d + 1 characters by Fact 1 and have length at least d+1d + 1, so the task (lt,gt,d+1)(lt, gt, d + 1) is valid. If v=−1v = -1, the middle range is not pushed.
  • The new ranges are disjoint and inside [lo,hi)[lo, hi), so (G1) holds.
  • For (G2), take i<ji < j in [lo,hi)[lo, hi) that are not in the same new task. If they are in different ranges, then c(A[i],d)<c(A[j],d)c(A[i], d) < c(A[j], d), and A[i]<A[j]A[i] < A[j] by Fact 1. If both are in the middle range and v=−1v = -1, then A[i]=A[j]A[i] = A[j] by Fact 1. A pair with one index outside [lo,hi)[lo, hi) keeps its relation from before, because the range holds the same strings: the order between A[j]A[j] outside the range and every string of the range was the same for all strings of the range, by (G2) before the pop.

Termination. Let LL be the largest length of a string. A valid task has strings of length at least dd, so d≤Ld \le L for each task with at least one string. Give a task (lo,hi,d)(lo, hi, d) the weight (hi−lo)(L+2−d)≥0(hi - lo)(L + 2 - d) \ge 0, and let WW be the total weight of the tasks on SS. Pop a task with m=hi−lo≥2m = hi - lo \ge 2 strings, and let m<m_<, m=m_= and m>m_> be the sizes of its three ranges, with m=≥1m_= \ge 1 because the pivot string is in the middle range. The children have the total weight at most (m<+m>)(L+2−d)+m=(L+1−d)=m(L+2−d)−m=(m_< + m_>)(L + 2 - d) + m_=(L + 1 - d) = m(L + 2 - d) - m_=, so WW drops by at least 1. A pop of a task with at most one string does not increase WW. So at most W0=n(L+2)W_0 = n(L + 2) pops handle a task with two or more strings. Each of them pushes at most three tasks, so there are at most 1+3n(L+2)1 + 3n(L + 2) pops, and the loop ends.

Result. When SS is empty, (G2) holds for all pairs i<ji < j, so AA is sorted, and by (G3) it holds the input strings. ■\blacksquare

Back to Multikey quicksort