Algorithm Search⌘K

Proofs: Weak heapsort

These are the human proofs of Weak heapsort, at full length.

Weak heapsort sorts the array full

This proof shows: correctness.

Notation. For a node ss of the tree on the indices 0,1,…0, 1, \ldots, let T(s)T(s) be the set of indices in the subtree of ss. The children of ss are 2s2s and 2s+12s + 1 whatever the bit r[s]r[s], so T(s)T(s) does not depend on the bits; only the names "left" and "right" do, and those of ss depend only on r[s]r[s]. The bit r[0]r[0] is never flipped, because 0 is never the second argument of JOIN; so node 1 is the right child of the root. For a heap of size mm we only look at the indices below mm. We use this property of a node s≥1s \ge 1:

R(s): A[d(s)]≥A[k]A[d(s)] \ge A[k] for each k∈T(s)k \in T(s).

If R(c) holds for the right child cc of ss, then d(c)=sd(c) = s and A[s]A[s] is at least each item of the right subtree of ss. If R(1) holds, A[0]A[0] is at least each item of the heap.

Lemma (a join step). Let j≥1j \ge 1 and d=d(j)d = d(j), and suppose R(s) holds for each proper descendant ss of jj. After JOIN(d,j)(d, j), R(s) holds for jj and for each proper descendant of jj. Moreover, for each node ss outside T(j)T(j), if R(s) held before, it still holds.

Proof. Let uu and vv be the items at dd and jj before the join. Let cc be the right child of jj and zz the left child (either may be missing). Then d(c)=jd(c) = j, so R(c) gives v≥v \ge each item of T(c)T(c). And zz is a left child, so the climb from zz goes through jj, and d(z)=d(j)=dd(z) = d(j) = d; so R(z) gives u≥u \ge each item of T(z)T(z).

No swap, so u≥vu \ge v. Nothing changes. Now u≥vu \ge v, u≥T(z)u \ge T(z) and u≥v≥T(c)u \ge v \ge T(c), so R(j) holds. The other statements did not change.

Swap, so u<vu < v. Now A[d]=vA[d] = v, A[j]=uA[j] = u, and r[j]r[j] is flipped, so zz is the right child and cc the left child of jj. R(j): A[d]=vA[d] = v is at least uu, at least T(c)T(c), and at least u≥T(z)u \ge T(z). For a proper descendant ss of jj, the climb from ss to d(s)d(s) changes only if it passes through cc or zz as a child of jj:

  • If ss is on the left spine of cc, then before the join d(s)=jd(s) = j; now cc is a left child, the climb goes on through jj, and d(s)=dd(s) = d, with A[d]=v≥T(c)⊇T(s)A[d] = v \ge T(c) \supseteq T(s).
  • If ss is on the left spine of zz, then before the join d(s)=dd(s) = d; now zz is a right child, so d(s)=jd(s) = j, with A[j]=u≥T(z)⊇T(s)A[j] = u \ge T(z) \supseteq T(s).
  • Otherwise d(s)d(s) is a proper descendant of jj, its item did not change, and T(s)T(s) does not contain dd or jj. So R(s) still holds.

For a node ss outside T(j)T(j): if ss is not an ancestor of jj, then T(s)T(s) contains neither jj nor dd (as dd is an ancestor of jj, or T(s)T(s) would contain jj) and d(s)d(s) is neither, except perhaps d(s)=dd(s) = d, where the item only grew. If ss is an ancestor of jj, then d(s)d(s) is not jj; if T(s)T(s) contains dd the two changed items stay in T(s)T(s), and its set of items is the same; if T(s)T(s) does not contain dd, then ss is strictly between dd and jj, so d(s)=dd(s) = d, whose item grew, and the item at jj fell. In each case R(s) still holds. □\square

The build. We claim that after the step of line L3 for jj, R(s) holds for each s≥js \ge j. For j=n−1j = n - 1 before the step, there are no nodes above n−1n - 1. Each proper descendant of jj has a larger index, so the lemma applies, and it gives R for jj and for all its descendants, and keeps R for the other nodes s>js > j. After j=1j = 1, R holds for all nodes, so A[0..n−1]A[0..n-1] is a weak heap.

The sort. Suppose that before line L5, R holds for each node of the heap A[0…m]A[0 \ldots m]. Then A[0]A[0] is its largest item, and line L5 puts it at index mm; the heap is now A[0…m−1]A[0 \ldots m-1]. Removing index mm only shrinks the sets T(s)T(s). The item at 0 changed, so R(s) may fail only for the nodes with d(s)=0d(s) = 0, which are the nodes 1=s1,s2,…,st1 = s_1, s_2, \ldots, s_t of the left spine of node 1 below mm, the nodes found by line L6. Lines L7 and L8 join st,st−1,…,s1s_t, s_{t-1}, \ldots, s_1 with the root in this order. When sis_i is joined, its proper descendants are T(si+1)T(s_{i+1}), where R holds by the lemma for the earlier step, and the nodes of its right subtree, whose distinguished ancestors are not 0, so R held for them all along. The lemma applies, and after s1s_1, R holds for all nodes of A[0…m−1]A[0 \ldots m-1].

Theorem. WEAK-HEAPSORT(A)(A) sorts AA and keeps its items.

Proof. All changes are swaps, so the multiset of items is kept. By the build and by induction over the steps of line L4, each step puts the largest item of A[0…m]A[0 \ldots m] at index mm, and the later steps work only inside A[0…m−1]A[0 \ldots m-1]. So A[m]≥A[k]A[m] \ge A[k] for each k<mk < m, for m≥2m \ge 2. Before line L9, R(1) holds for the heap A[0…1]A[0 \ldots 1], so A[0]≥A[1]A[0] \ge A[1], and the swap orders them. So the array is sorted. ■\blacksquare

Back to Weak heapsort