Algorithm Search⌘K

Proofs: Cache-oblivious B-tree

These are the human proofs of Cache-oblivious B-tree, at full length.

The index stays exact after every update, and the search finds the predecessor full

This proof shows: correctness, termination.

We use the pseudocode of the page. The PMA has 2H2^H segments of SS cells. The index is a complete binary tree with H+1H + 1 levels; the node at depth dd with path pp has the segments p⋅2H−dp \cdot 2^{H-d} to (p+1)2H−d−1(p+1) 2^{H-d} - 1 below it. Its position in the index array is POS(H+1,d,p)\mathrm{POS}(H+1, d, p), which is a bijection onto {0,…,2H+1−2}\{0, \ldots, 2^{H+1} - 2\} by the proof on the van Emde Boas layout page, so distinct nodes have distinct cells. The PMA keeps its key sequence strictly increasing and equal to the stored set, and every operation of the PMA ends; both facts are proved on the packed memory array page. We write (C1) for this, and (C2), (C3) for the conditions on the index from the page. For a node vv, let K(v)K(v) be the set of keys in the segments below vv.

Lemma 1. If (C1), (C2) and (C3) hold, then for every node vv, min⁡(v)\min(v) is the smallest element of K(v)K(v), or none if K(v)K(v) is empty.

Proof. By induction on the height of vv. For a leaf, this is (C2) with (C1): the first key of a segment is its smallest key. For an internal node, K(v)=K(left(v))∪K(right(v))K(v) = K(\text{left}(v)) \cup K(\text{right}(v)), and by (C1) every key of the left part is smaller than every key of the right part, since the left segments come first. So the smallest element of K(v)K(v) is that of the left part if it is not empty, and that of the right part otherwise, which is (C3) with the induction hypothesis. □\square

Lemma 2 (UPDATE). Suppose (C2) and (C3) held, and then the PMA changed only the cells of segments s0s_0 to s1s_1. After UPDATE(s0..s1)(s_0 .. s_1), (C2) and (C3) hold.

Proof. Call a node changed if one of the segments s0,…,s1s_0, \ldots, s_1 is below it. A node that is not changed has the same segments below it with the same contents, and its children are not changed either; its value was right before and still is, since (C2) and (C3) for it refer only to unchanged things. Line L11 sets every changed leaf by (C2). The changed nodes at depth d<Hd < H are exactly those with pp from ⌊s0/2H−d⌋\lfloor s_0 / 2^{H-d} \rfloor to ⌊s1/2H−d⌋\lfloor s_1 / 2^{H-d} \rfloor, which is the range of line L13. Lines L12 and L13 go from depth H−1H - 1 up to 0, so when a node at depth dd is set, both its children, at depth d+1d + 1, already have their final values: they were set in the pass before, or at line L11, or they are not changed. So (C3) holds at each changed node after it is set, and later passes do not change it again. □\square

Lemma 3 (BUILD). After BUILD, (C2) and (C3) hold.

Proof. Line L14 calls UPDATE on all segments, so every node is changed and set, leaves first and then by increasing height, as in Lemma 2. □\square

Lemma 4 (SEARCH). If (C1), (C2) and (C3) hold, SEARCH(k)(k) returns the position of the largest key at most kk, or none if there is none.

Proof. If no key is at most kk, the scan of line L4 finds no key at most kk in any segment, so SEARCH returns none, which is right. Otherwise let π\pi be the largest key at most kk. We show the invariant at the start of each pass of line L2: π∈K(v)\pi \in K(v). At the root, K(v)K(v) holds all keys.

Consider a pass at node vv with π∈K(v)\pi \in K(v). If min⁡(right(v))≠\min(\text{right}(v)) \ne none and min⁡(right(v))≤k\min(\text{right}(v)) \le k, then by Lemma 1 the right part has a key at most kk, and every key of the left part is smaller than it by (C1); so π\pi, the largest key at most kk, is not in the left part, and π∈K(right(v))\pi \in K(\text{right}(v)). Otherwise, by Lemma 1, the right part is empty or all its keys exceed kk, so π\pi is not in it, and π∈K(left(v))\pi \in K(\text{left}(v)). In both cases line L3 moves to the child that holds π\pi.

The loop ends after HH passes, since each moves one level down. At the leaf, π\pi is in the segment, whose keys are in order by (C1), and no key after π\pi in it is at most kk. So the scan of line L4 returns the position of π\pi. □\square

Lemma 5 (INSERT and DELETE). If (C1) to (C3) hold before an update, they hold after it, and the set changes as specified.

Proof. By Lemma 4, line L5 or L9 finds the predecessor of kk, which is the position that the PMA insert and delete use; so by the proof of the PMA, (C1) holds after the PMA step and the set changes as specified. If the PMA resized, the index is built again, and Lemma 3 gives (C2) and (C3). Otherwise, the PMA changed only the cells of the window WW, which is a run of whole segments; for a delete without a rebalance, WW is the segment of the deleted cell. Lines L8 and L10 call UPDATE on exactly these segments, and Lemma 2 gives (C2) and (C3). □\square

Termination. SEARCH makes HH passes and scans SS cells. UPDATE runs over finite ranges. The PMA steps end, by the proof of the PMA. So every operation ends.

The empty structure is built by BUILD, so (C1) to (C3) hold at the start. By Lemma 5 they hold after every update, and by Lemma 4 every predecessor query is right; contains and range follow from it and from (C1), as for the PMA. ■\blacksquare

Back to Cache-oblivious B-tree