Algorithm Search⌘K

Proofs: Java system sort

These are the human proofs of Java system sort, at full length.

Both paths of the Java system sort sort the array, and the object path is stable full

This proof shows: correctness, termination.

Line L1 picks one of two paths. We prove each path correct. In both, each change of AA is an exchange, a move inside a range, or a merge of two neighbouring ranges, so AA always holds a permutation of the input.

The primitive path

Claim P. SORT-PRIMITIVE(A,lo,hi,depth)(A, lo, hi, depth) stops and leaves A[lo…hi]A[lo \ldots hi] sorted, and it changes no item outside the range.

Proof. By induction on hi−lohi - lo, and for equal lengths on depthdepth. If hi−lo<47hi - lo < 47, line L3 calls insertion sort, which is correct. If depth=0depth = 0, line L4 calls heapsort, which is correct. Otherwise hi−lo≥47hi - lo \ge 47.

Line L5. The five sample indices e3−2s,…,e3+2se_3 - 2s, \ldots, e_3 + 2s with s=⌊(hi−lo+1)/7⌋≥6s = \lfloor (hi - lo + 1)/7 \rfloor \ge 6 and e3=⌊(lo+hi)/2⌋e_3 = \lfloor (lo + hi)/2 \rfloor lie strictly between lolo and hihi and are distinct. Sorting them by insertion and exchanging the second with A[lo]A[lo] and the fourth with A[hi]A[hi] gives p=A[lo]≤q=A[hi]p = A[lo] \le q = A[hi].

Line L6. The loop keeps the invariant

(D) A[lo+1…lt−1]<pA[lo+1 \ldots lt-1] < p,   p≤A[lt…k−1]≤q\; p \le A[lt \ldots k-1] \le q,   A[gt+1…hi−1]>q\; A[gt+1 \ldots hi-1] > q,   lo+1≤lt≤k≤gt+1≤hi\; lo + 1 \le lt \le k \le gt + 1 \le hi.

It holds at the start with lt=k=lo+1lt = k = lo + 1 and gt=hi−1gt = hi - 1, since the three parts are empty. Consider one step with k≤gtk \le gt and x=A[k]x = A[k].

  • If x<px < p, exchanging A[k]A[k] and A[lt]A[lt] moves xx to the end of the left part, and the item at ltlt, which was in the middle part or was xx itself when lt=klt = k, moves to kk. Then ltlt and kk both grow by 1, and (D) holds.
  • If x>qx > q, the inner loop moves gtgt left past the items greater than qq, but not below kk. These items join the right part. If it stops with k<gtk < gt, then A[gt]≤qA[gt] \le q, and the exchange puts xx at gtgt, at the left end of the right part, and A[gt]A[gt] at kk. If it stops with k=gtk = gt, the exchange does nothing and xx at gtgt joins the right part. In both cases gtgt drops by 1. The new item at kk is at most qq, or k>gtk > gt. If it is less than pp, it goes to the left part as in the first case. Then kk grows by 1, and (D) holds.
  • Otherwise p≤x≤qp \le x \le q, and kk grows by 1, which adds xx to the middle part.

Each step raises kk or lowers gtgt (in fact raises kk), so the loop stops with k=gt+1k = gt + 1.

Line L7. At the end, lt−1lt - 1 is the last index of the left part, or lolo if it is empty, and gt+1gt + 1 is the first index of the right part, or hihi. After lt←lt−1lt \leftarrow lt - 1 and gt←gt+1gt \leftarrow gt + 1, exchanging A[lo]A[lo] with A[lt]A[lt] puts pp at ltlt and an item less than pp, or pp itself, at lolo. Exchanging A[hi]A[hi] with A[gt]A[gt] puts qq at gtgt and an item greater than qq, or qq itself, at hihi. Now A[lo…lt−1]<p=A[lt]≤A[lt+1…gt−1]≤q=A[gt]<A[gt+1…hi]A[lo \ldots lt-1] < p = A[lt] \le A[lt+1 \ldots gt-1] \le q = A[gt] < A[gt+1 \ldots hi], with lt<gtlt < gt.

Lines L8 and L9. The three ranges are strictly shorter than A[lo…hi]A[lo \ldots hi], because each misses at least the two pivots. By the induction hypothesis the calls stop and sort them. If p=qp = q, every item of the middle part is from pp to qq, so equal to pp, and the part is sorted without a call. Each item of a part is at most each item of the parts to its right, by the inequalities above, so A[lo…hi]A[lo \ldots hi] is sorted. □\square

Line L1 calls SORT-PRIMITIVE on the whole array, so by Claim P the array is sorted.

The object path

Invariant (R). At the start of each step of the loop of line L10, with ii the start of the next run, the stack holds runs [s1,ℓ1],…,[sm,ℓm][s_1, \ell_1], \ldots, [s_m, \ell_m] with s1=0s_1 = 0, st+1=st+ℓts_{t+1} = s_t + \ell_t and sm+ℓm=is_m + \ell_m = i, each A[st…st+ℓt−1]A[s_t \ldots s_t + \ell_t - 1] is sorted, and the items of each run are the items that the input held at the same positions, in an order that keeps equal items in their input order.

Initialization. The stack is empty and i=0i = 0.

Line L10. The run at ii is ascending, or strictly descending and then reversed. A reversal of a strictly descending sequence gives an ascending sequence, and it has no equal items, so stability is kept. Binary insertion of each later item xx finds the first index whose item is greater than xx, so xx goes after all items equal to it, which came before it in the input, and the prefix stays sorted. So the new run is sorted and stable, and it starts at ii.

Lines L11 and L12. The push keeps (R). A merge of the runs kk and k+1k + 1 takes two neighbouring sorted runs and writes their items into the same positions in sorted order. It takes the item of the left run when the two items are equal, so it keeps equal items in the order of the runs, which is their input order by (R). The two runs become one run with the start of the first and the sum of the lengths, so (R) holds.

Termination. Each step of the loop of line L10 adds a run of length at least 1, so ii grows, and the loop stops when i=ni = n. Each merge removes one run from the stack, so the loops of line L11 and line L12 stop.

Correctness at the end. Line L12 merges until one run is left. By (R), it starts at 0, has length nn, is sorted, and keeps equal items in their input order. So the array is sorted, and the sort is stable. If n=0n = 0 the stack stays empty and there is nothing to sort. ■\blacksquare

The stack invariant of line L11 is not needed for correctness, only for the bound of O(log⁡n)O(\log n) runs on the stack and for the cost of O(nlog⁡n)O(n \log n).

Back to Java system sort