Algorithm Search⌘K

Proofs: Block sort

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

Block sort returns the stable sorted order full

This proof shows: correctness, termination.

Tag each item with its input position. A sequence is stably sorted if its keys do not decrease and items with equal keys are in increasing order of their tags. For items u,vu, v write u⊏vu \sqsubset v when uu belongs before vv in the stable order: the key of uu is less than that of vv, or the keys are equal and the tag of uu is smaller. This is a strict total order on the items, and "stably sorted" means increasing in ⊏\sqsubset.

We use these facts from other pages: the rotation by three reversals, and the rotation merge with its stability, from in-place merge sort; insertion sort is correct; LOWER-BOUND on a sorted range returns the first place whose item is not less than the key.

Termination. Every loop runs over a finite range with an index that moves one way, and the only recursion is the rotation merge, which ends. So BLOCK-SORT ends.

The outer sort. By induction on the levels, at the start of the level with width ww each run A[jw…(j+1)w−1]A[jw \ldots (j+1)w - 1] is stably sorted, and the tags in one run are smaller than those in the next. For w=16w = 16 this is line L1. A pair of runs that line L4 skips has its last left item not greater than its first right item, so its concatenation is already stably sorted. Any other pair is merged by BLOCK-MERGE, which by the theorem below makes it stably sorted. When w≥nw \ge n there is one run. It remains to prove the theorem.

Theorem. Let P=A[lo…mid−1]P = A[lo \ldots mid-1] and Q=A[mid…hi−1]Q = A[mid \ldots hi-1] be stably sorted, with all tags of PP smaller than all tags of QQ, and m=∣P∣≥∣Q∣≥1m = |P| \ge |Q| \ge 1. Then BLOCK-MERGE leaves A[lo…hi−1]A[lo \ldots hi-1] stably sorted and changes nothing outside it.

If m<64m < 64, line L5 is the rotation merge, which is stable. Otherwise we follow the lines.

Line L7. The scan keeps the invariant: the found items form one block, in increasing order, each the first item of PP with its key, and the other items of PP seen so far keep their relative order, before the block. An item is added when its key is greater than the last found key; since PP is sorted, it is then the first copy of a new key. A rotation of the block forward past the gap keeps the order of the gap items. At the end, a rotation puts the block at the front. So A[lo…lo+f−1]A[lo \ldots lo+f-1] holds the first copies c1⊏⋯⊏cfc_1 \sqsubset \cdots \sqsubset c_f of the ff smallest keys of PP, and P′=A[lo+f…mid−1]P' = A[lo+f \ldots mid-1] is PP without them, still stably sorted.

Lemma 1 (putting the buffer back). Let A[b…b+f−1]A[b \ldots b+f-1] hold distinct keys c1<⋯<cfc_1 < \cdots < c_f, each the first copy of its key in PP, and let A[b+f…hi−1]A[b+f \ldots hi-1] hold the stably sorted order UU of the other items of P∪QP \cup Q. Then the rolling loop of redistribute in line L17 leaves the stably sorted order of all of them.

Proof. For each cic_i, the items of P∪QP \cup Q with the same key all have larger tags than cic_i: those in PP because cic_i is the first copy in PP, and those in QQ because all tags of QQ are larger. So in the stable order cic_i goes right before the first item of UU that is not less than it, which is what LOWER-BOUND finds. The loop handles c1c_1 first. It rotates the block past the items of UU that are less than c1c_1, which keeps their order, and leaves c1c_1 right before the first item that is not less than it. Then c1c_1 is fixed, and the remaining block c2,…c_2, \ldots continues from there; the items it passes are greater than c1c_1 and are still in stable order. The keys cic_i increase, so each cic_i lands after ci−1c_{i-1}, and by induction the result is the stable order. □\square

Line L8. If f<kf < k, the rotation merge stably merges P′P' and QQ into UU, and Lemma 1 finishes. From now on f=k=3sf = k = 3s with s=⌈m⌉s = \lceil \sqrt{m} \rceil.

Line L10. P′P' has m−3sm - 3s items. It is cut into RR with r<sr < s items followed by pp A-blocks of ss items, and QQ into qq B-blocks of ss items followed by the tail WW with fewer than ss items. There are t=p+q≤(m+∣Q∣)/s≤2m/s≤2st = p + q \le (m + |Q|)/s \le 2m/s \le 2s blocks, so there are enough tags. Tag jj is the jj-th smallest buffer key, and it is set beside block jj, in the order A-blocks, then B-blocks. When q>0q > 0, let τ\tau be the tag of the first B-block; a block is an A-block if and only if its tag is less than τ\tau, and this stays true as tags move with blocks.

Lemma 2 (the block order). After line L12, list the blocks, with WW among them, as Z1,…,ZuZ_1, \ldots, Z_u from left to right, and let h(Z)h(Z) be the first item of ZZ. Then (a) the blocks from one run are in their original order, and (b) if i<ji < j, then h(Zj)⊏̸h(Zi)h(Z_j) \not\sqsubset h(Z_i) when ZiZ_i and ZjZ_j come from different runs.

Proof. Line L11 is a selection sort on the key (first item, tag) compared as in the code: ZZ before Z′Z' if h(Z)<h(Z′)h(Z) < h(Z'), or the keys are equal and the tag of ZZ is smaller. The tags are distinct, so this is a strict total order, and selection sort produces it; exchanging blocks of equal length with their tags keeps each block intact. For blocks of one run, the first items increase in ⊏\sqsubset with the original order, and on equal keys the tag order is the original order, so (a) holds. For blocks from different runs with equal first keys, the A-block has the smaller tag and comes first, and its first item also has the smaller input position; so the order of line L11 agrees with ⊏\sqsubset on first items, which gives (b).

For WW: every B-block comes before WW in QQ, so h(B)⊏h(W)h(B) \sqsubset h(W) for each B-block BB. Line L12 puts WW just before the longest suffix of blocks that are all A-blocks whose first key is greater than the key of h(W)h(W). So each block ZZ after WW is an A-block with h(W)⊏h(Z)h(W) \sqsubset h(Z). Now let ZZ be an A-block before WW, and let YY be the block just before WW. If YY is an A-block, its key is at most that of h(W)h(W), and by (a) so is the key of h(Z)h(Z); since h(Z)h(Z) is in PP and h(W)h(W) in QQ, h(Z)⊏h(W)h(Z) \sqsubset h(W). If YY is a B-block, then ZZ is before YY, and by (b) h(Z)⊏h(Y)⊏h(W)h(Z) \sqsubset h(Y) \sqsubset h(W). So (b) holds with WW placed, and (a) holds because WW is after every B-block. □\square

Lemma 3 (the local merges). Before each step of lines L14 and L15, the array range of the blocks reads: the items made final so far, in stable order; then the swap space SS; then FF, a stably sorted suffix of the items of one run seen so far; then Z,Znext,…Z, Z_{\text{next}}, \ldots in place. Every final item is ⊏\sqsubset every item of FF and of the blocks not yet handled.

Proof. At the start nothing is final and F=RF = R. RR is the beginning of P′P', so its items are ⊏\sqsubset every other item of P′P', but not necessarily of QQ; this is all the invariant needs, since nothing is final yet.

Same run (line L14). ZZ is the next block of FF's run, by Lemma 2(a), so every item xx of FF has x⊏h(Z)x \sqsubset h(Z) and is ⊏\sqsubset every later item of its own run. A later item yy of the other run lies in a block Z′Z' after ZZ, and by Lemma 2(b), h(Z′)⊏̸h(Z)h(Z') \not\sqsubset h(Z); the two are distinct items, so h(Z)⊏h(Z′)h(Z) \sqsubset h(Z'), and x⊏h(Z)⊏h(Z′)⊑yx \sqsubset h(Z) \sqsubset h(Z') \sqsubseteq y. So all of FF may become final. Moving FF before SS by exchanges keeps its order, and the new F=ZF = Z is a suffix of its run seen so far.

Different runs (line L15). The loop writes, at each step, the ⊏\sqsubset-smaller of the heads of FF and ZZ; the test in the code is exactly this, as ties of keys go to the item from PP, which has the smaller tag. Each write exchanges an item of SS with the chosen item. Let g=∣Z∣≤sg = |Z| \le s. The writing place starts ss places before the head of FF and gains one place on it for each item taken from ZZ, so it reaches the unread items only after all of ZZ is read. So no unread item is overwritten, and the items of SS are only moved. The loop stops when one side is used up; two sides cannot run out at the same step, as each step takes one item. If FF was empty, nothing is written. Otherwise let ee be the head of the rest of the other side, and uu the last item of the used-up side, which was the last item written. Every written item is ⊏e\sqsubset e, and ee is ⊏\sqsubset every later item of its run. Every written item is ⊑u\sqsubseteq u, and uu is ⊏\sqsubset every later item of its own run, because the blocks of a run come in order. So the written items are ⊏\sqsubset everything not yet handled, and they may become final. If FF was used up, the code leaves SS as one block followed by the rest of ZZ. If ZZ was used up, the code moves the rest of FF to the right end with exchanges in decreasing order of position, which keeps its order and leaves SS as one block before it. In both cases the new FF is a suffix of one run seen so far. □\square

Lines L16 and L17. After the last block, line L16 makes FF final by Lemma 3 with no blocks left, so the range of the blocks is the stable order of P′∪QP' \cup Q followed by SS. The rotation puts SS next to TT, so the buffer of kk distinct items is at the front, in some order, followed by the stable order of the other items. Insertion sort puts the buffer in increasing order, which is its stable order because the keys are distinct. Lemma 1 then gives the stable order of all of P∪QP \cup Q. Only A[lo…hi−1]A[lo \ldots hi-1] was touched. This proves the theorem, and with the outer induction, BLOCK-SORT leaves AA stably sorted. ■\blacksquare

Back to Block sort