Algorithm Search⌘K

Proofs: Weiner's algorithm

These are the human proofs of Weiner's algorithm, at full length.

Weiner's algorithm builds the suffix tree in linear time full

This proof shows: correctness, termination, complexity.

Let t=S$t = S\$, ∣t∣=n+1|t| = n + 1, with $\$ only at the end, and ti=t[i…n]t_i = t[i \ldots n]. Let Ti\mathcal{T}_i be the compact trie of ti,…,tnt_i, \ldots, t_n. Since $\$ is unique, no suffix is a prefix of another, and each has a leaf. For a string β\beta that is a prefix of some tjt_j, j≥ij \ge i, call β\beta present in Ti\mathcal{T}_i. Write str(v)\mathrm{str}(v) for the string of a node.

Definitions at time ii (after inserting tit_i). For an inner node or the root vv of Ti\mathcal{T}_i: Iv={x:x str(v) is present in Ti}I_v = \{x : x\,\mathrm{str}(v) \text{ is present in } \mathcal{T}_i\}, and Lv(x)L_v(x) is the node of x str(v)x\,\mathrm{str}(v) if that string is a node of Ti\mathcal{T}_i, undefined otherwise.

Invariant. After the step for ii (and after line L1 for i=ni = n), the tree is Ti\mathcal{T}_i, and II and LL match the definitions at time ii.

For i=ni = n: the tree is the root with the leaf of $\$, Iroot={$}I_{root} = \{\$\}, and no link is defined, since no nonempty string is an inner node.

Lemma 1 (branching is inherited to the right). If xβx\beta is an inner node of Ti\mathcal{T}_i, then β\beta is an inner node of Ti\mathcal{T}_i (or the root, if β\beta is empty).

Proof. xβcx\beta c and xβdx\beta d are present for two symbols c≠dc \ne d, as prefixes of tjt_j and tkt_k. Then βc\beta c and βd\beta d are prefixes of tj+1t_{j+1} and tk+1t_{k+1}, which are in the tree since j+1,k+1≤nj + 1, k + 1 \le n (a suffix with xβcx\beta c as a prefix is not the last one). So β\beta branches. □\square

Lemma 2 (the head). Fix i<ni < n and x=t[i]x = t[i], and let the tree be Ti+1\mathcal{T}_{i+1}. Let vv be the lowest inner node or root on the path of ti+1t_{i+1} with x∈Ivx \in I_v, if one exists. Then the longest prefix of tit_i present in Ti+1\mathcal{T}_{i+1} is x str(v)x\,\mathrm{str}(v) if vv exists, and the empty string otherwise.

Proof. A nonempty prefix of ti=x ti+1t_i = x\,t_{i+1} present in Ti+1\mathcal{T}_{i+1} has the form xβx\beta with β\beta a prefix of ti+1t_{i+1}, and it is present exactly when β\beta has xx in front in some tjt_j, j>ij > i. Presence of xβx\beta is closed under taking prefixes. Let β\beta be the longest one and suppose it lies strictly below vv on the path of ti+1t_{i+1}; let cc be the first node at or below the place of β\beta. Every suffix tj+1t_{j+1} that has β\beta as a prefix passes through cc (no branching between), and j+1≠i+1j + 1 \ne i + 1, so if cc is the leaf of ti+1t_{i+1} this is impossible; otherwise x str(c)x\,\mathrm{str}(c) is a prefix of tjt_j, so x∈Icx \in I_c, and cc is lower than vv, a contradiction. If no node has xx, then even the root lacks xx, so xx alone is not present. □\square

By Lemma 2 and the uniqueness of $\$, the head of tit_i (its longest prefix present in Ti+1\mathcal{T}_{i+1}) is shorter than tit_i, so adding tit_i creates one leaf, below the place of the head, and at most one inner node, at the head.

Step ii, line L4. The walk starts at the parent of the leaf of ti+1t_{i+1}; the leaf itself cannot have xx in front, because only ti+1t_{i+1} passes through it and x ti+1=tix\,t_{i+1} = t_i is not yet present. Since II is closed upward (if x str(v)x\,\mathrm{str}(v) is present, so is xx times each prefix), the walk stops at the lowest node vv with x∈Ivx \in I_v, or at the root without xx. After tit_i is inserted, xβx\beta is present for every prefix β\beta of ti+1t_{i+1}, so xx must be in IwI_w for every node ww on the path of ti+1t_{i+1}; the nodes above vv already have it, and line L4 adds it to the nodes passed and to vv or the root. No other indicator changes for old nodes: the only new present strings are the prefixes of tit_i, and a string y str(w)y\,\mathrm{str}(w) that is a prefix of tit_i has y=xy = x and str(w)\mathrm{str}(w) a prefix of ti+1t_{i+1}.

Line L5. If no vv exists, the head is empty by Lemma 2, and the leaf of tit_i goes below the root. No new inner node and no new link are created: the only new node is the leaf.

Lines L6 to L9. Let v′v' be the lowest node at or above vv with Lv′(x)L_{v'}(x) defined, or ⊥\bot if there is none, and v′′=Lv′(x)v'' = L_{v'}(x), so str(v′′)=x str(v′)\mathrm{str}(v'') = x\,\mathrm{str}(v') (with str(⊥)\mathrm{str}(\bot) of length −1-1 and $v'' = $ root). The head x str(v)x\,\mathrm{str}(v) is present and lies on the path from v′′v'' that starts with the symbols of str(v)\mathrm{str}(v) after str(v′)\mathrm{str}(v'). No node lies strictly between v′′v'' and the place of the head: such a node would be xβx\beta with str(v′)⊊β⊊str(v)\mathrm{str}(v') \subsetneq \beta \subsetneq \mathrm{str}(v) (as prefixes), and by Lemma 1 β\beta would be an inner node, strictly between v′v' and vv and with a link for xx, contradicting the choice of v′v'; for v′=⊥v' = \bot, the same argument with β\beta ranging over the prefixes of str(v)\mathrm{str}(v) including the empty one, whose link would make the root the choice of v′v'. If g=0g = 0, then v=v′v = v' and the head is the node v′′v''. If g>0g > 0, the head is not a node of Ti+1\mathcal{T}_{i+1} (it would give Lv(x)L_v(x), so v′=vv' = v), so it is inside the edge entered from v′′v'' by the symbol t[i+1+∣v′∣]t[i + 1 + |v'|], and the split creates the node hh of the head; walking down by gg symbols uses only the edge length.

Updates of II and LL for the new node. In Ti\mathcal{T}_i, hh has two children: the old lower node cc and the leaf of tit_i. A string y str(h)y\,\mathrm{str}(h) is present in Ti\mathcal{T}_i if and only if it is a prefix of some tjt_j, j≥ij \ge i, with str(h)\mathrm{str}(h) a prefix of tj+1t_{j+1}; for j=ij = i this would need ti−1t_{i-1}, so j>ij > i and tj+1t_{j+1} is an old suffix through hh, hence through cc. So Ih=IcI_h = I_c at time ii, where for a leaf cc of tkt_k the set IcI_c is {t[k−1]}\{t[k-1]\} when k≥1k \ge 1 (tk−1t_{k-1} is in Ti\mathcal{T}_i because k−1≥ik - 1 \ge i) and empty for k=0k = 0. Note that IcI_c for an inner node cc is already correct at time ii after line L4, since cc is not on the path of ti+1t_{i+1} unless it was updated there, and in both cases it matches the definition. No string y str(h)y\,\mathrm{str}(h) is a node of Ti\mathcal{T}_i: by Lemma 1, str(h)\mathrm{str}(h) would have been an inner node of Ti+1\mathcal{T}_{i+1}, but it is new. So LhL_h is empty. The only new inner node is hh, and its only incoming link is from the node of str(h)\mathrm{str}(h) without its first symbol, which is vv; line L9 sets Lv(x)=hL_v(x) = h. Old links stay correct, since nodes are never removed and an old node keeps its string.

Line L10. The leaf of tit_i goes below the head, which gives Ti\mathcal{T}_i. The invariant holds after step ii, and after step 0 the tree is T0\mathcal{T}_0, the suffix tree of S$S\$. The loop has nn steps, and each walk goes up a finite path, so the algorithm terminates.

Time. Let nd(w)nd(w) be the number of nodes on the root path of ww, with nd(⊥)=0nd(\bot) = 0.

Links raise the node depth by at most one. If Lu(x)=wL_u(x) = w, each node on the root path of ww other than the root is xβx\beta for a node β\beta on the root path of uu (Lemma 1), and distinct nodes give distinct β\beta. So nd(w)−1≤nd(u)nd(w) - 1 \le nd(u). For ⊥\bot, nd(root)=1=nd(⊥)+1nd(\text{root}) = 1 = nd(\bot) + 1.

Walks. Lines L4 and L6 pass from the parent of the leaf ℓi+1\ell_{i+1} of ti+1t_{i+1} up to v′v', which is nd(ℓi+1)−nd(v′)−1nd(\ell_{i+1}) - nd(v') - 1 steps, or up to the root in the case of line L5. The new leaf ℓi\ell_i has nd(ℓi)≤nd(v′′)+2≤nd(v′)+3nd(\ell_i) \le nd(v'') + 2 \le nd(v') + 3 (the split adds at most one node above it), and nd(ℓi)=2nd(\ell_i) = 2 in the case of line L5. In both cases the walk of step ii has at most nd(ℓi+1)−nd(ℓi)+3nd(\ell_{i+1}) - nd(\ell_i) + 3 steps, and the sum over all ii is at most nd(ℓn)+3n=O(n)nd(\ell_n) + 3n = O(n).

Sets. Each indicator set only grows, and the copy in line L9 inserts exactly as many entries as it reads, so the total cost of all set operations is OO of the final number of entries plus the number of walk steps. The entries at the end are the pairs (v,x)(v, x) with vv an inner node or the root and x str(v)x\,\mathrm{str}(v) present. Reverse all strings: str(v)R\mathrm{str}(v)^R is a string of tRt^R that is left-maximal (it has two different symbols on its left in tRt^R, or is the empty string), and xx is a symbol after it. Each pair is either an edge of the suffix tree of tRt^R (when str(v)R\mathrm{str}(v)^R is right-maximal too, it is a node and xx starts one of its child edges) or the unique right extension of a left-maximal string that is not right-maximal, at most one per inner node vv. The suffix tree of tRt^R has O(n)O(n) edges and the suffix tree of tt has O(n)O(n) nodes, so there are O(n)O(n) entries.

Total. O(n)O(n) steps, each with O(1)O(1) map operations: O(n)O(n) time for a constant alphabet or expected with hashing, and O(nlog⁡σ)O(n \log \sigma) with balanced maps. ■\blacksquare

Back to Weiner's algorithm