Algorithm Search⌘K

Proofs: Persistent binary search tree

These are the human proofs of Persistent binary search tree, at full length.

Path-copying insertion and deletion keep the search order and every old version full

This proof shows: correctness.

Write K(t)K(t) for the set of keys in the subtree of tt, with K(nil)=∅K(\text{nil}) = \emptyset. A tree tt is a search tree if (B1) holds at each node of it. For an interval I=(a,b)I = (a, b) with a<ba < b in the order extended by −∞-\infty and +∞+\infty, say that tt fits II if tt is a search tree and K(t)⊆IK(t) \subseteq I.

Lemma 0 (old versions do not change). Let vv be a root that exists at some moment. Then the tree reachable from vv is the same at each later moment.

Proof. The procedures write fields only in NEW-NODE, which sets the fields of a node that it has just made. So no field of an existing node changes (B2). The tree reachable from vv is determined by the fields of the nodes reachable from vv, which all exist at that moment. □\square

So we can reason about each call as if it ran on a fixed tree, and its result does not affect any other version.

Lemma 1 (DESCEND). Let tt be a search tree and kk a key. DESCEND(t,k)(t, k) returns (path,x)(path, x) where path=[y1,…,yd]path = [y_1, \ldots, y_d], y1=ty_1 = t (if d≥1d \ge 1), each yi+1y_{i+1} is the child of yiy_i on the side of kk (left if k<yi.keyk < y_i.key, else right), k≠yi.keyk \ne y_i.key for each ii, and xx is the child of ydy_d on the side of kk (or x=tx = t when d=0d = 0). Moreover x≠nilx \ne \text{nil} exactly when k∈K(t)k \in K(t), and then x.key=kx.key = k.

Proof. The loop of lines L16 to L18 keeps the statement for the nodes appended so far, with the current tt in the role of xx. It stops when t=nilt = \text{nil} or t.key=kt.key = k, and it stops after at most hh steps, because each step goes one level down. If it stops at a node of key kk, then k∈K(t)k \in K(t). If it stops at nil, then k∉K(t)k \notin K(t): by (B1), at each yiy_i the key kk can only be in the subtree on the side of kk, so if kk were in K(t)K(t), it would be in the subtree of the final position, which is empty. □\square

For the path of Lemma 1, define the interval I0=(−∞,+∞)I_0 = (-\infty, +\infty), and Ii=(ai,bi)I_i = (a_i, b_i) where bi=yi.keyb_i = y_i.key and ai=ai−1a_i = a_{i-1} if k<yi.keyk < y_i.key, and ai=yi.keya_i = y_i.key and bi=bi−1b_i = b_{i-1} otherwise. By (B1), the subtree of the child of yiy_i on the side of kk fits IiI_i, and k∈Iik \in I_i for each ii by induction.

Lemma 2 (REBUILD). Let tt be a search tree, let $(path, x) = $ DESCEND(t,k)(t, k) with d=∣path∣d = |path|, and let cc fit IdI_d. Then $r = $ REBUILD(path,k,c)(path, k, c) is a search tree with K(r)=(K(t)∖K(x))∪K(c)K(r) = (K(t) \setminus K(x)) \cup K(c), where for d=0d = 0 we read r=cr = c and K(t)=K(x)K(t) = K(x).

Proof. By induction on the iterations of line L20, from i=di = d down to i=1i = 1. Let cd=cc_d = c, and let ci−1c_{i-1} be the copy of yiy_i that the iteration for yiy_i makes. We show that ci−1c_{i-1} fits Ii−1I_{i-1} and has the keys (K(yi)∖K(x))∪K(c)(K(y_i) \setminus K(x)) \cup K(c).

Take the case k<yi.keyk < y_i.key (line L21); the other case (line L22) is the mirror image. The copy has key yi.keyy_i.key, left child cic_i and right child yi.righty_i.right. The left child fits Ii=(ai−1,yi.key)I_i = (a_{i-1}, y_i.key) by the hypothesis (or by the assumption for i=di = d), so all its keys are below yi.keyy_i.key and above ai−1a_{i-1}. The right child is the old right subtree of yiy_i, which is a search tree whose keys are above yi.keyy_i.key and, since yiy_i lies in a subtree that fits Ii−1I_{i-1}, below bi−1b_{i-1}. And yi.key∈Ii−1y_i.key \in I_{i-1} for the same reason. So the copy is a search tree and fits Ii−1I_{i-1}. Its keys are {yi.key}∪K(yi.right)∪K(ci)\{y_i.key\} \cup K(y_i.right) \cup K(c_i). The old subtree had {yi.key}∪K(yi.right)∪K(yi+1)\{y_i.key\} \cup K(y_i.right) \cup K(y_{i+1}) (with xx in place of yi+1y_{i+1} for i=di = d), and by the hypothesis K(ci)=(K(yi+1)∖K(x))∪K(c)K(c_i) = (K(y_{i+1}) \setminus K(x)) \cup K(c), which gives the claim, because K(x)⊆K(yi+1)K(x) \subseteq K(y_{i+1}) is disjoint from {yi.key}∪K(yi.right)\{y_i.key\} \cup K(y_i.right).

At i=1i = 1 we get r=c0r = c_0, which fits I0I_0 and has the keys (K(t)∖K(x))∪K(c)(K(t) \setminus K(x)) \cup K(c). □\square

Theorem 1 (INSERT). If tt is a search tree, INSERT(t,k)(t, k) returns a search tree with keys K(t)∪{k}K(t) \cup \{k\}, and tt is unchanged.

Proof. If k∈K(t)k \in K(t), Lemma 1 gives x≠nilx \ne \text{nil}, and line L5 returns tt. Else x=nilx = \text{nil}, so K(x)=∅K(x) = \emptyset, and the leaf with key kk fits IdI_d because k∈Idk \in I_d. Lemma 2 gives a search tree with keys K(t)∪{k}K(t) \cup \{k\}. Lemma 0 gives that tt is unchanged. □\square

Theorem 2 (DELETE). If tt is a search tree, DELETE(t,k)(t, k) returns a search tree with keys K(t)∖{k}K(t) \setminus \{k\}, and tt is unchanged.

Proof. If k∉K(t)k \notin K(t), line L8 returns tt. Else xx is the node of key kk, and the subtree of xx fits IdI_d. We build cc that fits IdI_d with K(c)=K(x)∖{k}K(c) = K(x) \setminus \{k\}. Then Lemma 2 gives the result.

If x.left=nilx.left = \text{nil} (line L9), c=x.rightc = x.right is a subtree of xx, so it fits IdI_d, and K(c)=K(x)∖{k}K(c) = K(x) \setminus \{k\}. Line L10 is the mirror image.

If xx has two children (lines L11 to L13), let yy be the last node of the left steps from x.rightx.right, so y.left=nily.left = \text{nil} and y.keyy.key is the least key of K(x.right)K(x.right). Let qq be the nodes before yy on these steps. This is the path that DESCEND(x.right,y.key)(x.right, y.key) returns, with yy as its node: at each node of qq, y.keyy.key is smaller, so the search goes left. By Lemma 2 applied to the search tree x.rightx.right with c=y.rightc = y.right (which fits the interval of yy on that path, being a subtree of yy), the tree $r' = $ REBUILD(q,y.key,y.right)(q, y.key, y.right) is a search tree with keys K(x.right)∖{y.key}K(x.right) \setminus \{y.key\}. The new node of line L13 has key y.keyy.key, left child x.leftx.left, whose keys are below k<y.keyk < y.key, and right child r′r', whose keys are in K(x.right)K(x.right) and greater than the least one, y.keyy.key. So it is a search tree, with keys K(x.left)∪K(x.right)=K(x)∖{k}K(x.left) \cup K(x.right) = K(x) \setminus \{k\}, all in IdI_d. □\square

Corollary (full persistence). For each sequence of operations in which each update takes any earlier version, every version vv is at the end a search tree whose key set is the result of applying, in order, the updates on the chain from the empty tree to vv, and SEARCH(v,k)(v, k) returns true exactly when kk is in this set.

Proof. By induction on the number of updates, with Theorems 1 and 2 for the step and Lemma 0 for the fact that later updates change no earlier version. SEARCH returns x≠nilx \ne \text{nil} at line L3 with the same loop as DESCEND, so by Lemma 1 it is right. ■\blacksquare

Back to Persistent binary search tree