Algorithm Search⌘K

Proofs: Radix tree

These are the human proofs of Radix tree, at full length.

The radix tree operations keep the invariant and answer the string symbol table queries full

This proof shows: correctness.

We use the pseudocode and the invariant (R1) to (R4) of the page. For a node xx, str(x)str(x) is the concatenation of the labels from the root to xx. Each node yy other than the root has a parent pp, and str(y)=str(p)⋅e.labelstr(y) = str(p) \cdot e.label for the edge ee from pp to yy, with ∣e.label∣≥1\lvert e.label \rvert \ge 1 by (R1). So ∣str∣\lvert str \rvert strictly increases down each path.

Lemma 1 (uniqueness and walk). Under (R1), for each string ss at most one node xx has str(x)=sstr(x) = s. The loop of lines L1 to L5 returns at line L4 if no node has str=sstr = s, and it ends at that node otherwise.

Proof. Suppose x≠x′x \ne x' with str(x)=str(x′)=sstr(x) = str(x') = s. Let yy be their lowest common ancestor. Since ∣str∣\lvert str \rvert grows strictly down a path, neither node is an ancestor of the other, so yy has two distinct children c,c′c, c' on the paths to xx and x′x'. Both labels are nonempty and both are followed in ss at position ∣str(y)∣\lvert str(y) \rvert, so they start with the same symbol s[∣str(y)∣]s[\lvert str(y) \rvert], which (R1) forbids. For the walk: the loop keeps "str(x)=s[0…i−1]str(x) = s[0 \ldots i-1]". At a node xx, a node zz below xx with s[0…i−1]≺str(z)⪯ss[0 \ldots i-1] \prec str(z) \preceq s must lie below the child edge whose label starts with s[i]s[i]; by (R1) that is at most the edge ee of line L3. If ee is NIL, no node lies below xx on the way to ss. If e.labele.label is not a prefix of s[i…]s[i \ldots], then str(e.child)str(e.child) is not a prefix of ss, and neither is the strstr of any node below e.childe.child, since all of them extend str(e.child)str(e.child). In both cases no node has str=sstr = s. Else line L5 moves to e.childe.child with str(e.child)=s[0…i+∣e.label∣−1]str(e.child) = s[0 \ldots i + \lvert e.label \rvert - 1]. The quantity ∣s∣−i\lvert s \rvert - i drops by at least 1 each round, so the loop ends. □\square

GET. By Lemma 1 and (R2), line L6 returns the value of ss if ss is a key, and NIL otherwise (no node, or a node with no value).

PUT. The loop keeps "str(x)=s[0…i−1]str(x) = s[0 \ldots i-1], the invariant (R1) holds, and the multiset of keys and values is the old one". Line L10: no edge from xx starts with s[i]s[i], so the new edge keeps (R1); the new leaf has str=sstr = s and value vv, and ss was not a key, since by Lemma 1 no node had str=sstr = s (that node would lie below an edge of xx starting with s[i]s[i]). So (R2) and (R4) hold, and (R3) holds because the new node has a value. Line L11: ee starts with s[i]s[i], so j≥1j \ge 1. If j<∣e.label∣j < \lvert e.label \rvert, lines L13 and L14 replace the edge x→e.childx \to e.child with label ℓ\ell by the edges x→zx \to z with label ℓ[0…j−1]\ell[0 \ldots j-1] and z→e.childz \to e.child with label ℓ[j…]\ell[j \ldots]. Both labels are nonempty, the first starts with the same symbol as ℓ\ell, and zz has one edge, so (R1) holds. The strstr of each old node is the same, so the keys are the same. The new node zz has no value and one child, so (R3) fails at zz only. Line L15 moves to the child with str=s[0…i+j−1]str = s[0 \ldots i + j - 1]. We show that the same call repairs zz. If i+j=∣s∣i + j = \lvert s \rvert, the loop ends with x=zx = z, and line L17 gives zz a value. Else s[i+j]≠ℓ[j]s[i + j] \ne \ell[j], by the choice of jj as the longest common prefix, so the next round finds no edge of zz starting with s[i+j]s[i + j] and line L10 adds a second edge to zz. If j=∣e.label∣j = \lvert e.label \rvert, no split is done and the walk goes on. When the loop ends without line L10, xx is the node with str(x)=sstr(x) = s. Lines L16 and L17 give it the value vv and count it if it had no value, so (R2) and (R4) hold, and (R3) holds at xx since it has a value.

DELETE. If line L19 returns, ss is not a key by Lemma 1 and (R2), and nothing changes. Else line L20 removes the value of the node xx with str(x)=sstr(x) = s and updates the count, so (R2) and (R4) hold for the keys without ss. Now (R3) can fail only at xx. Two cases.

  • xx has no edge. If xx is the root, (R3) asks nothing. Else line L21 removes the leaf xx, which has no value, so no key is lost. Its parent pp lost one child. If pp is not the root and has no value, then before the removal pp had at least two children by (R3), so it has at least one now. If it has two or more, (R3) holds. If it has exactly one, line L22 merges it as in the next case.
  • xx has exactly one edge ff, xx is not the root, and xx has no value. Let ee be the edge from the parent qq of xx to xx. Line L22 replaces ee and ff by one edge from qq to f.childf.child with label e.label⋅f.labele.label \cdot f.label. The new label starts with the first symbol of e.labele.label, so (R1) holds at qq. The strstr of f.childf.child and of each node below it is unchanged, and the only removed node is xx, which has no value. So the keys are unchanged, and (R3) holds, since xx is gone and no other node lost a child.
  • xx has two or more edges. Then (R3) holds at xx, and line L22 does nothing.

KEYS-WITH-PREFIX. Let pp be the query. Line L23 walks as in Lemma 1. If the walk ends exactly at a node xx, then str(x)=pstr(x) = p. If pp ends inside the label of an edge ee to a child cc, so that p≺str(c)p \prec str(c), then each node yy with p⪯str(y)p \preceq str(y) is cc or below cc: its path from the root shares the part up to the parent of cc, and by (R1) it then takes ee. In both cases, the nodes yy with p⪯str(y)p \preceq str(y) are exactly the nodes of the subtree of the reached node xx. If the walk finds no edge, or a label that disagrees with pp before pp ends, then by the argument of Lemma 1 no node has strstr with prefix pp, and the empty list is right. The stack holds pairs (y,str(y))(y, str(y)), because line L28 pushes str(y)⋅f.label=str(f.child)str(y) \cdot f.label = str(f.child). Each node of the subtree is pushed once. So by (R2), line L27 appends exactly the keys with prefix pp. The children are popped in increasing order of their first symbol, and by (R1) the first symbols differ, so the preorder lists the strings in lexicographic order, as in the proof for the trie. ■\blacksquare

Node bound. If (R3) holds and the tree has n≥1n \ge 1 keys, then it has at most 2n2n nodes. Proof. Each leaf other than the root has a value, so there are at most nn leaves, or the root alone. In a rooted tree, let LL, UU and BB be the numbers of nodes with zero, one, and two or more children. The tree has L+U+B−1L + U + B - 1 edges, and each edge goes to a child, so the number of edges is the sum of the numbers of children, which is at least U+2BU + 2B. So L+U+B−1≥U+2BL + U + B - 1 \ge U + 2B, that is, B≤L−1B \le L - 1. The nodes without a value other than the root have at least two children, so there are at most n−1n - 1 of them, and with the root and the at most nn nodes with values the total is at most 2n2n. □\square

Back to Radix tree