Algorithm Search⌘K

Proofs: Weak heap

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

The weak-heap operations keep weak-heap order full

This proof shows: correctness.

The array a[0..n−1]a[0..n-1] with bits r[0..n−1]r[0..n-1] and r[0]=0r[0] = 0 defines a tree: the left child of ii is 2i+r[i]2i + r[i] and the right child is 2i+1−r[i]2i + 1 - r[i], where they exist, and node 0 has only the child 1. For a node xx, T(x)T(x) is the set of nodes of the subtree of xx, and L(x)L(x) and R(x)R(x) are the sets of nodes of its left and right subtrees. Flipping r[x]r[x] exchanges L(x)L(x) and R(x)R(x) and changes no other subtree as a set. Write C(x)C(x) for the condition a[x]≤a[y]a[x] \le a[y] for all y∈R(x)y \in R(x). The heap is in weak-heap order if C(x)C(x) holds for every node xx. A set SS of nodes is ordered if C(x)C(x) holds for every x∈Sx \in S. For a set SS, write a[i]≤Sa[i] \le S if a[i]≤a[y]a[i] \le a[y] for every y∈Sy \in S.

Lemma 1 (distinguished ancestors). For j>0j > 0, let d(j)d(j) be the value of D-ANCESTOR(j)(j). Then j∈R(d(j))j \in R(d(j)), and for every node xx, j∈R(x)j \in R(x) if and only if xx is one of d(j),d(d(j)),…d(j), d(d(j)), \ldots. If jj has a left child yy, then d(y)=d(j)d(y) = d(j).

Proof. Line L1 goes up while the current node cc is a left child: cc is the left child of its parent exactly when c mod 2=r[⌊c/2⌋]c \bmod 2 = r[\lfloor c/2 \rfloor]. It stops at a node cc that is a right child, and d(j)d(j) is its parent, so j∈T(c)=R(d(j))j \in T(c) = R(d(j)). Every ancestor zz of jj strictly between d(j)d(j) and jj is on the walk, so jj is in L(z)L(z), not in R(z)R(z). So d(j)d(j) is the lowest ancestor xx with j∈R(x)j \in R(x). If j∈R(x)j \in R(x) and x≠d(j)x \ne d(j), then xx is a proper ancestor of d(j)d(j) and d(j)∈R(x)d(j) \in R(x), and the claim follows by induction on depth. The converse holds because R(d(z))⊇T(z)∋jR(d(z)) \supseteq T(z) \ni j for each zz on the chain. For the left child yy of jj, the walk from yy first goes up to jj, and then continues as the walk from jj. □\square

Lemma 2 (join). Let i=d(j)i = d(j). Before JOIN(i,j)(i, j), assume that T(j)T(j) is ordered and a[i]≤L(j)a[i] \le L(j). After the join, T(j)T(j) is ordered and a[i]≤T(j)a[i] \le T(j). The join changes only a[i]a[i], a[j]a[j] and r[j]r[j], and it does not increase a[i]a[i].

Proof. If a[j]≥a[i]a[j] \ge a[i], nothing changes, and a[i]≤a[j]≤R(j)a[i] \le a[j] \le R(j) by C(j)C(j), so a[i]≤T(j)a[i] \le T(j).

Otherwise line L4 swaps the keys and flips r[j]r[j]. Let uu and vv be the old a[i]a[i] and a[j]a[j], with v<uv < u. The new a[i]=va[i] = v is less than u≤L(j)u \le L(j), it is at most every key of the old R(j)R(j) by the old C(j)C(j), and it is less than the new a[j]=ua[j] = u. So the new a[i]≤T(j)a[i] \le T(j). The new right subtree of jj is the old L(j)L(j), and the new a[j]=u≤a[j] = u \le the old L(j)L(j), so C(j)C(j) holds. For x∈T(j)x \in T(j) with x≠jx \ne j, the set R(x)R(x) and its keys did not change, because i∉T(j)i \notin T(j), so C(x)C(x) still holds. □\square

Theorem 1 (build). After lines L5 and L6, the heap is in weak-heap order, with exactly n−1n - 1 comparisons.

Proof. A join at jj changes only a[d(j)]a[d(j)], a[j]a[j] and r[j]r[j], and d(j)<jd(j) < j. So once jj has been processed, no later step (which has a smaller index) changes a key or a bit in T(j)T(j): every node of T(j)T(j) has an index at least jj, and the later steps change only nodes with smaller indices. We prove by induction downward on jj: when jj is processed, T(j)T(j) is ordered and a[d(j)]≤L(j)a[d(j)] \le L(j).

T(j)T(j) is ordered: for every x∈T(j)x \in T(j) with x≠jx \ne j, the step at xx came earlier and left T(x)T(x) ordered by Lemma 2, and T(x)T(x) has not changed since. For C(j)C(j): if jj has a right child zz, then d(z)=jd(z) = j by Lemma 1, so the step at zz gave a[j]≤T(z)=R(j)a[j] \le T(z) = R(j). After it, T(z)T(z) did not change, and a[j]a[j] changed only at steps (j,z′)(j, z') for other z′>jz' > j, which do not increase it.

a[d(j)]≤L(j)a[d(j)] \le L(j): if jj has a left child yy, then d(y)=d(j)d(y) = d(j) by Lemma 1, and the step at yy gave a[d(j)]≤T(y)=L(j)a[d(j)] \le T(y) = L(j). Since then, T(y)T(y) has not changed, and a[d(j)]a[d(j)] changed only as the first argument of joins, which do not increase it, because d(j)<jd(j) < j and d(j)d(j) is processed after jj.

So Lemma 2 applies at every step. After the step at j=1j = 1, T(1)T(1) is ordered and a[0]≤T(1)=R(0)a[0] \le T(1) = R(0), so C(0)C(0) also holds. Each of the n−1n - 1 steps makes one comparison. □\square

Theorem 2 (sift-down). Assume C(x)C(x) holds for every x≠0x \ne 0 among the nodes 0,…,m−10, \ldots, m - 1, with m≥2m \ge 2. After SIFT-DOWN(m)(m), the heap is in weak-heap order.

Proof. Line L8 finds the path s1=1,s2,…,sks_1 = 1, s_2, \ldots, s_k, where st+1s_{t+1} is the left child of sts_t and sks_k has no left child. By Lemma 1, d(st)=0d(s_t) = 0 for every tt. Lines L9 and L10 call JOIN(0,sk)(0, s_k), then JOIN(0,sk−1)(0, s_{k-1}), up to JOIN(0,s1)(0, s_1), because ⌊st+1/2⌋=st\lfloor s_{t+1}/2 \rfloor = s_t. We show by induction, for t=kt = k down to 1, that after JOIN(0,st)(0, s_t) the set T(st)T(s_t) is ordered and a[0]≤T(st)a[0] \le T(s_t).

For t=kt = k: T(sk)T(s_k) is ordered by assumption, and L(sk)L(s_k) is empty, so Lemma 2 applies. For t<kt < k: the earlier joins changed only a[0]a[0] and nodes in T(st+1)T(s_{t+1}), and the bits there. So C(st)C(s_t) still holds, because R(st)R(s_t) and a[st]a[s_t] have not changed, and the nodes of R(st)R(s_t) are still ordered. With the induction hypothesis, T(st)={st}∪R(st)∪T(st+1)T(s_t) = \{s_t\} \cup R(s_t) \cup T(s_{t+1}) is ordered, and L(st)=T(st+1)L(s_t) = T(s_{t+1}), so a[0]≤L(st)a[0] \le L(s_t). Lemma 2 applies again.

At the end, T(1)T(1) is ordered and a[0]≤T(1)=R(0)a[0] \le T(1) = R(0), so every node satisfies CC. □\square

Theorem 3 (delete-min). DELETE-MIN returns a smallest key, and the remaining heap of n−1n - 1 keys is in weak-heap order.

Proof. The root is a smallest key, because R(0)R(0) holds all other nodes. Removing the last node, a leaf, removes one element from some sets R(x)R(x) and keeps every C(x)C(x). Moving its key to the root changes no C(x)C(x) for x≠0x \ne 0, because node 0 is in no R(x)R(x). With at least two nodes left, Theorem 2 gives the result; with one node, there is nothing to check. □\square

Theorem 4 (insert). After INSERT(k)(k), the heap is in weak-heap order and holds the old keys and kk.

Proof. Line L13 puts kk in the new leaf j=nj = n. If jj is even, jj is the first child of its parent pp, which was a leaf, so setting r[p]=0r[p] = 0 changes no subtree of the old tree and makes jj the left child of pp.

We show that the loop keeps this invariant I(j)I(j): for every node xx and every y∈R(x)y \in R(x) with y≠jy \ne j, a[x]≤a[y]a[x] \le a[y]. In words, the only possible violations are between jj and the nodes xx with j∈R(x)j \in R(x). After line L13, I(j)I(j) holds, because the old heap was in weak-heap order and the new leaf has R(j)=∅R(j) = \emptyset.

Let i=d(j)i = d(j), and assume I(j)I(j). If JOIN(i,j)(i, j) does not swap, then a[i]≤a[j]a[i] \le a[j]. By Lemma 1, every other xx with j∈R(x)j \in R(x) has i∈R(x)i \in R(x), so a[x]≤a[i]≤a[j]a[x] \le a[i] \le a[j] by I(j)I(j). Every C(x)C(x) holds, and the loop stops.

If the join swaps, let uu and vv be the old a[i]a[i] and a[j]a[j], with v<uv < u. We check I(i)I(i).

  • x=jx = j: the new R(j)R(j) is the old L(j)L(j), which is part of R(i)R(i) and does not contain jj, so u≤u \le the old L(j)L(j) by I(j)I(j).
  • x=ix = i: R(i)R(i) is the same set. Its keys other than a[j]a[j] did not change and are at least u>vu > v. The new a[j]=u>va[j] = u > v.
  • x∈T(j)x \in T(j), x≠jx \ne j: R(x)R(x) and its keys did not change, and j∉R(x)j \notin R(x).
  • xx with i∈R(x)i \in R(x): these xx are proper ancestors of ii, so j∈R(x)j \in R(x) as well. The pair (x,i)(x, i) is the exception allowed by I(i)I(i). For y=jy = j, the new key is uu, and a[x]≤ua[x] \le u held before, because i≠ji \ne j and i∈R(x)i \in R(x).
  • Every other xx: if R(x)R(x) meets T(j)T(j), then R(x)⊇T(j)∋jR(x) \supseteq T(j) \ni j, and by Lemma 1 x=ix = i or i∈R(x)i \in R(x), which are the cases above. Otherwise R(x)R(x) contains neither ii nor jj and did not change.

So I(i)I(i) holds, and the loop continues with j←ij \leftarrow i. Each step moves to a proper ancestor, so the loop ends: either at a join without a swap, which gives weak-heap order as shown, or with j=0j = 0. Node 0 is in no R(x)R(x), so I(0)I(0) is the weak-heap order itself. Joins only exchange keys, so the multiset of keys is the old keys and kk. ■\blacksquare

Back to Weak heap