Algorithm Search⌘K

Proofs: Dijkstra's three-way partition

These are the human proofs of Dijkstra's three-way partition, at full length.

Dijkstra's three-way partition is correct and inspects each item exactly once full

This proof shows: correctness, termination.

We use the pseudocode of the page. Let A0A_0 be the input array, of length n≥0n \ge 0, and pp the pivot value. Write X<pX < p for "every item of the range XX is less than pp", and the same for == and >>; an empty range satisfies each of these.

Claim. THREE-WAY-PARTITION(A,p)(A, p) runs the loop of line L2 exactly nn times. On return, AA is a permutation of A0A_0, and the returned pair (lt,gt)(lt, gt) satisfies A[0…lt−1]<pA[0 \ldots lt-1] < p, A[lt…gt−1]=pA[lt \ldots gt-1] = p and A[gt…n−1]>pA[gt \ldots n-1] > p.

Proof. We show that this invariant holds at each test of line L2:

(F) 0≤lo≤mid≤hi+1≤n0 \le lo \le mid \le hi + 1 \le n; AA is a permutation of A0A_0; A[0…lo−1]<pA[0 \ldots lo-1] < p; A[lo…mid−1]=pA[lo \ldots mid-1] = p; A[hi+1…n−1]>pA[hi+1 \ldots n-1] > p.

Initialization. Line L1 sets lo=mid=0lo = mid = 0 and hi=n−1hi = n - 1. Then 0≤0≤0≤n≤n0 \le 0 \le 0 \le n \le n, A=A0A = A_0, and the three ranges A[0…−1]A[0 \ldots -1], A[0…−1]A[0 \ldots -1] and A[n…n−1]A[n \ldots n-1] are empty.

Maintenance. Assume (F) and mid≤himid \le hi, so 0≤mid≤n−10 \le mid \le n - 1 and A[mid]A[mid] exists. Let x=A[mid]x = A[mid]. Write primes for the values after the iteration.

  • Case x<px < p (line L4). If lo<midlo < mid, then lolo is in the range [lo,mid−1][lo, mid - 1], so A[lo]=pA[lo] = p by (F). After the swap, A′[lo]=x<pA'[lo] = x < p and A′[mid]=pA'[mid] = p. If lo=midlo = mid, the swap changes nothing and A′[lo]=x<pA'[lo] = x < p. In both cases the other cells do not change. Now lo′=lo+1lo' = lo + 1 and mid′=mid+1mid' = mid + 1. The range A′[0…lo′]A'[0 \ldots lo'] minus its last cell is A[0…lo−1]<pA[0 \ldots lo-1] < p, and its last cell A′[lo]<pA'[lo] < p, so A′[0…lo′−1]<pA'[0 \ldots lo'-1] < p. The range A′[lo′…mid′−1]=A′[lo+1…mid]A'[lo' \ldots mid'-1] = A'[lo+1 \ldots mid] is the range A[lo+1…mid−1]A[lo+1 \ldots mid-1], which is =p= p, plus the cell A′[mid]A'[mid], which is pp when lo<midlo < mid; when lo=midlo = mid the range is empty. The last region does not change. The bounds hold since mid+1≤hi+1mid + 1 \le hi + 1.
  • Case x>px > p (line L6). After the swap, A′[hi]=x>pA'[hi] = x > p, and hi′=hi−1hi' = hi - 1. So A′[hi′+1…n−1]=A′[hi…n−1]A'[hi'+1 \ldots n-1] = A'[hi \ldots n-1] is the cell A′[hi]>pA'[hi] > p followed by A[hi+1…n−1]>pA[hi+1 \ldots n-1] > p. The cells 0…mid−10 \ldots mid-1 do not change, because mid≤himid \le hi and the swap touches only midmid and hihi. The bounds hold: mid≤hi=hi′+1mid \le hi = hi' + 1.
  • Case x=px = p (line L7). mid′=mid+1mid' = mid + 1, and A[lo…mid]=pA[lo \ldots mid] = p. Nothing else changes, and mid+1≤hi+1mid + 1 \le hi + 1.

Each case only swaps two cells, so A′A' is still a permutation of A0A_0. So (F) holds again.

Termination and count. Let u=hi−mid+1u = hi - mid + 1. By (F), u≥0u \ge 0, and the loop runs while u≥1u \ge 1. Line L4 adds 1 to midmid, line L6 subtracts 1 from hihi and line L7 adds 1 to midmid; in each case uu decreases by exactly 1. It starts at nn, so the loop runs exactly nn times and stops with u=0u = 0.

Result. When the loop stops, mid=hi+1mid = hi + 1. Line L8 returns lt=lolt = lo and gt=hi+1=midgt = hi + 1 = mid. By (F), A[0…lt−1]<pA[0 \ldots lt-1] < p, A[lt…gt−1]=A[lo…mid−1]=pA[lt \ldots gt-1] = A[lo \ldots mid-1] = p, and A[gt…n−1]=A[hi+1…n−1]>pA[gt \ldots n-1] = A[hi+1 \ldots n-1] > p, and AA is a permutation of A0A_0. ■\blacksquare

Each item is inspected once. The value A[mid]A[mid] is compared with pp once in each iteration. Call the cells mid,…,himid, \ldots, hi the unknown region. An item is compared only when it is at midmid, inside the unknown region, and after the comparison it leaves this region in each case: by mid+1mid + 1 in lines L4 and L7, and by the swap to position hihi and hi−1hi - 1 in line L6. Items enter the unknown region never; the item that line L6 moves to position midmid was already in it. So each of the nn items is compared in exactly one iteration, which matches the count nn.

Swap count. Line L4 runs once for each item less than pp and line L6 once for each item greater than pp, since each iteration handles the item it inspects. So the number of swaps is the number of items different from pp.

Back to Dijkstra's three-way partition