Algorithm Search⌘K

Proofs: Double-array trie

These are the human proofs of Double-array trie, at full length.

The double-array trie build establishes the invariant and lookups follow the trie full

This proof shows: correctness.

We use the pseudocode, the codes 0,…,R0, \ldots, R and the invariant (D1) to (D3) of the page. Let TT be the ordinary trie of the key set KK built at line L7, with an extra child on code 0 at each node whose path is a key. Write cell(u)cell(u) for the cell given to a node uu of TT.

Lemma 1 (the build gives each node of TT one cell, and no cell to two nodes). When the queue empties, each node of TT has a cell, the cells of distinct nodes are distinct, check[cell(u)]=cell(parent(u))check[cell(u)] = cell(parent(u)) for each non-root uu, and cell(u)=base[cell(parent(u))]+acell(u) = base[cell(parent(u))] + a where aa is the code of the edge into uu.

Proof. The root gets cell 0, with a reserved check. A node uu is dequeued once, because it is enqueued once, by its parent. When uu is dequeued at line L9, its children have no cells yet, and line L10 picks a base bb for which every cell b+ab + a, for aa in the codes of the children, is free. Line L11 marks these cells with the check cell(u)cell(u), so they are no longer free, and line L12 gives the cell b+ab + a to the child on aa. Cells that are taken are never freed, so no later node gets one of them. Each node of TT other than the root is the child of exactly one node, so it gets exactly one cell, at the offset of its code from the base of its parent. Such a base exists: the arrays grow, and a base past the last used cell has all its cells free. Each node's base is set once, when it is dequeued, before any of its children's cells are read. □\square

Lemma 2 (D1). For each node uu of TT and each code aa, DA-NEXT(cell(u),a)(cell(u), a) returns cell(w)cell(w) if uu has a child ww on aa, and NIL otherwise.

Proof. If ww exists, by Lemma 1 the cell t=base[cell(u)]+at = base[cell(u)] + a is cell(w)cell(w) and its check is cell(u)cell(u), so lines L1 and L2 return tt. If ww does not exist and tt is in range, the cell tt is free (check −1-1), or the cell of a node w′w' whose parent u′u' is not uu, or the root. In the first case the check is −1≠cell(u)-1 \ne cell(u). In the second, check[t]=cell(u′)check[t] = cell(u'), and cell(u′)≠cell(u)cell(u') \ne cell(u) by Lemma 1. The root cannot be tt, since bases are at least 1 and codes are nonnegative. So line L2 fails and line L3 returns NIL. If tt is out of range, line L3 returns NIL too. □\square

Lemma 1 also gives (D2): each cell other than the root is free or the cell of exactly one node, the one whose parent's cell is named by the check.

Lemma 3 (D3 and DA-GET). DA-GET(k)(k) returns the value of kk if k∈Kk \in K and NIL otherwise.

Proof. By Lemma 2 and induction on the symbols of kk, after line L4 has read jj symbols, ss is the cell of the node of TT whose path is the first jj symbols of kk, or the walk has returned NIL because no such node exists. In the second case kk is not a prefix of a path of TT, so k∉Kk \notin K. In the first case, after all symbols, s=cell(u)s = cell(u) for the node uu with path kk. By the construction of TT, uu has a child on code 0 if and only if k∈Kk \in K, so by Lemma 2 line L5 finds the leaf cell exactly when kk is a key. Line L12 stored the value of kk there when the leaf was placed, so line L6 returns it. □\square

Prefix searches. The common prefix search of a text xx is the walk of Lemma 3 on the symbols of xx with a test of code 0 after each step. By Lemma 3 applied to each prefix of xx, it reports exactly the keys that are prefixes of xx, and it stops at the first prefix that is not a path of TT, after which no longer prefix can be a key. Listing the keys below a node tests the codes 0,…,R0, \ldots, R at each node reached, and by Lemma 2 this finds exactly its children in TT; visiting them in increasing code order lists the keys in the order of the codes. ■\blacksquare

Back to Double-array trie