Algorithm Search⌘K

Proofs: String B-tree

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

Blind search, one comparison and the passed lcp locate a pattern in the String B-tree full

This proof shows: correctness, complexity.

We use the pseudocode, the representation and the invariant (S1) to (S4) of the page. Strings are compared lexicographically, with the end of a string below every symbol; lcp(u,v)lcp(u, v) is the length of the longest common prefix, and u[i]u[i] for i≥∣u∣i \ge \lvert u \rvert is the end symbol. For sorted strings k0≤⋯≤kc−1k_0 \le \cdots \le k_{c-1} and a<ba < b, lcp(ka,kb)=min⁡(adj[a+1],…,adj[b])lcp(k_a, k_b) = \min(adj[a+1], \ldots, adj[b]) (Fact 0, the standard property of sorted strings).

Lemma 1 (blind search). Lines L1 and L2 return a key ss with lcp(P,s)=max⁡klcp(P,k)lcp(P, s) = \max_k lcp(P, k).

Proof. Let ℓ∗=max⁡klcp(P,k)\ell^* = \max_k lcp(P, k). We show that the range kept by the loop always contains a key kk with lcp(P,k)=ℓ∗lcp(P, k) = \ell^*. It holds at the start. Take a range [lo,hi][lo, hi] with this property, and let dd be the minimum of adjadj in it, so all its keys share their first dd symbols (Fact 0), and the parts after the split are the groups of keys with the same symbol at position dd, with distinct symbols. If ℓ∗≤d\ell^* \le d, all keys of the range have the same lcp with PP, namely min⁡(lcp(P,k),d)\min(lcp(P, k), d) for any of them, which is ℓ∗\ell^*; so every part keeps the property. If ℓ∗>d\ell^* > d, a best key agrees with PP at position dd, so P[d]P[d] is the symbol of its part, and line L2 keeps that part. When the range has one key, it is a best key. □\square

Lemma 2 (the order from one comparison). Let ss be a key with ℓ=lcp(P,s)\ell = lcp(P, s) maximal among the keys. Then for every key kk, lcp(P,k)=min⁡(ℓ,lcp(s,k))lcp(P, k) = \min(\ell, lcp(s, k)), and line L4 decides k≤Pk \le P correctly.

Proof. Let c=lcp(s,k)c = lcp(s, k). If c<ℓc < \ell: PP and ss agree up to ℓ>c\ell > c, so PP and kk first differ at cc, with the same symbols as ss and kk; thus lcp(P,k)=clcp(P, k) = c and k≤Pk \le P exactly when k≤sk \le s, that is, when kk comes before ss in the node. If c>ℓc > \ell: kk agrees with ss beyond ℓ\ell, so lcp(P,k)=ℓlcp(P, k) = \ell and kk compares with PP like ss does, by s[ℓ]s[\ell] against P[ℓ]P[\ell]. If c=ℓc = \ell: lcp(P,k)≥ℓlcp(P, k) \ge \ell and, by the maximality of ℓ\ell, lcp(P,k)=ℓlcp(P, k) = \ell; then k≤Pk \le P is decided by k[ℓ]k[\ell] against P[ℓ]P[\ell], and k[ℓ]k[\ell] is a branching symbol of the trie: if kk is after ss, it is rc[j]rc[j] for the last j≤kj \le k with adj[j]≤cadj[j] \le c (the keys from jj to kk share more than cc symbols, by Fact 0), and symmetrically lc[j]lc[j] for the first j>kj > k with adj[j]≤cadj[j] \le c if kk is before ss. The two symbols k[ℓ]k[\ell] and P[ℓ]P[\ell] are equal only if both are the end symbol, and then k=Pk = P, which counts as k≤Pk \le P. □\square

Lemma 3 (the passed lcp). In RANK, the value λ\lambda passed to a node is at most max⁡klcp(P,k)\max_k lcp(P, k) over the keys kk of that node, so line L3, which starts reading at λ\lambda, computes lcp(P,s)lcp(P, s) correctly.

Proof. At the root λ=0\lambda = 0. At an internal node xx, if r=2i+1r = 2i + 1, then by (S3) the keys of xx are f0,g0,f1,g1,…f_0, g_0, f_1, g_1, \ldots (first and last strings of the children), and exactly f0,…,gi−1,fif_0, \ldots, g_{i-1}, f_i are ≤P\le P. So fi≤P<gif_i \le P < g_i. The value passed is max⁡(lcp(P,fi),lcp(P,gi))\max(lcp(P, f_i), lcp(P, g_i)), computed exactly by Lemma 2. Both fif_i and gig_i are keys of child ii: for a leaf they are its first and last strings, and for an internal child they are the first key of its first child and the last key of its last child, by (S3) applied to the child. So λ\lambda is at most the maximum over the child's keys. By Lemma 1 the blind search in the child returns a key ss with lcp(P,s)lcp(P, s) at least that maximum, hence at least λ\lambda, and the symbols before λ\lambda need not be read. □\square

RANK is correct. We show that the value returned is the number of stored strings ≤P\le P. At a node xx reached with rankrank equal to the number of strings in the leaves to the left of xx's subtree, all of which are ≤P\le P, and with all strings to the right of the subtree >P> P: by Lemma 2, rr is the number of keys of xx that are ≤P\le P. If xx is a leaf, its keys are its strings, so the answer is rank+rrank + r. Otherwise, let i=⌊r/2⌋i = \lfloor r / 2 \rfloor. If r=2ir = 2i, then gi−1≤P<fig_{i-1} \le P < f_i, so by (S2) all strings of children 0,…,i−10, \ldots, i-1 are ≤P\le P and all strings of children i,…i, \ldots are >P> P; the answer is rankrank plus their counts. If r=2i+1r = 2i + 1, then fi≤P<gif_i \le P < g_i: the children before ii are all ≤P\le P, those after are all >P> P, and the descent into child ii keeps the claim. Lemma 3 makes each comparison correct. The loop ends at a leaf at the latest.

PREFIX-RANGE. With ⊤\top above every symbol, a string xx is ≤P⊤\le P \top exactly when x<Px < P or PP is a prefix of xx. So the strings in the returned range of ranks are those ≥P\ge P with prefix PP, which by sorted order are exactly the strings with prefix PP.

INSERT keeps the invariant. The descent finds a leaf such that inserting ww at its rank in the leaf keeps the global order (S2): if ww falls inside the range of child ii, it must go into that child; if it falls between children i−1i - 1 and ii, the end of child i−1i - 1 (or the start of child 0) is a valid place. Line L9 inserts it at its rank among the leaf's strings, computed by Lemma 2. Line L10 restores the B-tree sizes (S1) by splitting an overfull child into two halves of at least ⌊b/2⌋\lfloor b / 2 \rfloor entries, in order, and recomputes the boundary keys (S3), the counts and the tries (S4) of every node on the path. Nodes off the path keep their strings, hence their boundaries and tries. Line L11 adds a level only at the root, so all leaves keep the same depth.

The number of symbols read. Let ℓj\ell_j be the value of ℓ\ell at the jj-th level of a search and λj\lambda_j the value passed to it. Lemma 3 gives λj+1=max⁡(lcp(P,fi),lcp(P,gi))\lambda_{j+1} = \max(lcp(P, f_i), lcp(P, g_i)), and since fif_i and gig_i are the neighbors of PP among the keys of the parent, the best key of the parent is one of them, so λj+1=ℓj\lambda_{j+1} = \ell_j. The comparison at level jj reads ℓj−λj\ell_j - \lambda_j matching symbols and at most one mismatching symbol. Summing, the search reads at most ℓh−λ1+h≤p+h\ell_h - \lambda_1 + h \le p + h symbols for a tree of height hh. ■\blacksquare

Back to String B-tree