Algorithm Search⌘K

Proofs: Dual-pivot quicksort

These are the human proofs of Dual-pivot quicksort, at full length.

Yaroslavskiy's partition splits the range into three parts, and dual-pivot quicksort sorts full

This proof shows: correctness, termination.

Fix a call PARTITION(A,lo,hi)(A, lo, hi) with lo<hilo < hi. Every change of AA is an exchange of two positions in [lo,hi][lo, hi], so the multiset of A[lo…hi]A[lo \ldots hi] never changes, and no position outside the range changes. After line L8, p=A[lo]≤q=A[hi]p = A[lo] \le q = A[hi], and these two positions are not touched by the loop, because all exchanges of lines L12, L15 and L17 use positions kk, ll and gg, which stay in [lo+1,hi−1][lo+1, hi-1], as the invariant shows.

Invariant. At each test of line L10:

(P) lo+1≤l≤klo + 1 \le l \le k, l≤g+1l \le g + 1, k≤g+2k \le g + 2 and g+1≤hig + 1 \le hi, and (a) each item of A[lo+1…l−1]A[lo+1 \ldots l-1] is less than pp; (b) each item of A[l…min⁡(k−1,g)]A[l \ldots \min(k-1, g)] is at least pp and at most qq; (c) each item of A[g+1…hi−1]A[g+1 \ldots hi-1] is at least qq.

Initialization. l=k=lo+1l = k = lo + 1 and g=hi−1g = hi - 1, so all three zones are empty, and lo+1≤hilo + 1 \le hi because lo<hilo < hi.

While k≤gk \le g, the zone (b) is A[l…k−1]A[l \ldots k-1], and A[k…g]A[k \ldots g] is the unseen zone.

Maintenance. Assume (P) and k≤gk \le g, and let v=A[k]v = A[k].

Case 1, v<pv < p (lines L11, L12). If l<kl < k, then A[l]A[l] is a middle item by (b), and the exchange puts vv at ll and the middle item at kk. If l=kl = k, the exchange does nothing. Then ll and kk grow by 1 (line L18). Zone (a) gains v<pv < p, and zone (b) moves one place right with the same items. (P) holds.

Case 2, p≤v<qp \le v < q (the tests of L11 and L13 both fail). Only kk grows, and zone (b) gains vv, with p≤v≤qp \le v \le q. (P) holds.

Case 3, v≥qv \ge q (line L13). The loop of line L14 moves gg left while A[g]>qA[g] > q and k<gk < g. Each position it passes holds an item greater than qq, so it can join zone (c). It stops at some g′≥kg' \ge k with A[g′]≤qA[g'] \le q or g′=kg' = k. Line L15 exchanges A[k]A[k] and A[g′]A[g'] and sets g←g′−1g \leftarrow g' - 1. Now A[g′+1]A[g'+1], which is the old A[k]=v≥qA[k] = v \ge q if g′>kg' > k or vv itself if g′=kg' = k, is at least qq, so zone (c) is A[g′…hi−1]A[g' \ldots hi-1] after the decrement, and (c) holds. The new A[k]A[k] is the old A[g′]A[g']. If g′=kg' = k it is vv again, line L16 does not move it because v≥q≥pv \ge q \ge p, and line L18 makes k=g+2k = g + 2. Then min⁡(k−1,g)=g\min(k-1, g) = g, so zone (b) is the same as before the step, and vv is in zone (c). (P) holds, and the loop ends. If g′>kg' > k, the new A[k]A[k] is at most qq. Line L16 tests it: if it is less than pp, line L17 moves it into zone (a) as in Case 1; otherwise it is between pp and qq and joins zone (b) when line L18 increments kk. In this subcase g≥kg \ge k after line L15, so after line L18 k≤g+1k \le g + 1, and (P) holds.

Termination. In each iteration, g−kg - k drops by at least 1, because kk grows by 1 and gg never grows. So the loop ends, with k=g+1k = g + 1 or k=g+2k = g + 2.

The result of PARTITION. At the end, k≥g+1k \ge g + 1, so zone (b) is A[l…g]A[l \ldots g], and the zones (a), (b), (c) cover A[lo+1…hi−1]A[lo+1 \ldots hi-1]. Line L19 sets l←l−1l \leftarrow l - 1 and g←g+1g \leftarrow g + 1. Now A[l]A[l] is the last item of zone (a), or A[lo]A[lo] itself if zone (a) is empty, and A[g]A[g] is the first item of zone (c), or A[hi]A[hi] itself if zone (c) is empty. Line L20 exchanges A[lo]A[lo] with A[l]A[l]: the item less than pp goes to lolo, inside the left part, and pp goes to ll. Likewise qq goes to gg, and the item at least qq goes to hihi. So:

A[l]=pA[l] = p, A[g]=qA[g] = q, lo≤l<g≤hilo \le l < g \le hi, each item of A[lo…l−1]A[lo \ldots l-1] is less than pp, each item of A[l+1…g−1]A[l+1 \ldots g-1] is between pp and qq, and each item of A[g+1…hi]A[g+1 \ldots hi] is at least qq.

Here l<gl < g because l≤g+1l \le g + 1 before line L19, so l−1<g+1l - 1 < g + 1.

DUAL-PIVOT-QUICKSORT sorts. By induction on m=hi−lo+1m = hi - lo + 1. For m≤1m \le 1, line L1 returns, and the range is sorted. For m≥2m \ge 2, line L2 only exchanges items inside the range, and PARTITION returns (l,g)(l, g) as above. The ranges [lo,l−1][lo, l-1], [l+1,g−1][l+1, g-1] and [g+1,hi][g+1, hi] do not contain ll or gg, so each has fewer than mm items, and by induction the three calls sort them. Each call changes only its own range, so the order relations above still hold: every item left of ll is less than pp, every item between ll and gg is in [p,q][p, q], and every item right of gg is at least qq. Together with the sorted parts this makes A[lo…hi]A[lo \ldots hi] non-decreasing.

Termination. Each call either returns at line L1 or makes three calls on strictly shorter ranges, and PARTITION ends. So the recursion is finite, whatever the random choices of line L2. ■\blacksquare

Back to Dual-pivot quicksort