Algorithm Search⌘K

Proofs: Suffix tree from suffix array

These are the human proofs of Suffix tree from suffix array, at full length.

The stack scan builds the suffix tree from the suffix array and the LCP array full

This proof shows: correctness, complexity.

We use the pseudocode and notation of the page. The text tt has length nn and ends with a unique end marker, so no suffix is a prefix of another. Write SkS_k for the suffix t[SA[k]…]t[SA[k] \ldots], so S0<S1<⋯<Sn−1S_0 < S_1 < \cdots < S_{n-1}, and lcp(x,y)lcp(x, y) for the length of the longest common prefix.

Fact 1. For k<ik < i, lcp(Sk,Si)=min⁡(LCP[k+1],…,LCP[i])≤LCP[i]lcp(S_k, S_i) = \min(LCP[k+1], \ldots, LCP[i]) \le LCP[i]. This is the standard property of sorted strings: the common prefix of SkS_k and SiS_i is a prefix of every string between them, and conversely the minimum over adjacent pairs is common to all of them.

Let Ti\mathcal{T}_i be the compacted trie of S0,…,Si−1S_0, \ldots, S_{i-1}: the tree whose nodes are the root, one leaf for each of these suffixes, and one internal node for each string ww that is the longest common prefix of two of them and is followed in them by two different symbols, with the children of each node in increasing order of their next symbol. (Here every leaf is a suffix, since none is a prefix of another.) Tn\mathcal{T}_n is the suffix tree of tt.

Invariant. Before iteration ii (for i≥1i \ge 1), the tree built is Ti\mathcal{T}_i, each node has the right depth, a correct start and its children in order, and the stack holds, from bottom to top, the nodes of the path from the root to the leaf of Si−1S_{i-1}. For i=0i = 0 the tree is the root alone and the stack is [root][root].

Iteration ii. Let h=LCP[i]h = LCP[i] (for i=0i = 0, h=0h = 0, and the leaf of S0S_0 becomes the only child of the root, which gives T1\mathcal{T}_1 with the stack [root,leaf][root, leaf]). For i≥1i \ge 1, Ti+1\mathcal{T}_{i+1} differs from Ti\mathcal{T}_i as follows. Let PP be the point at depth hh on the path from the root to the leaf of Si−1S_{i-1}; it exists because ∣Si−1∣>h\lvert S_{i-1} \rvert > h. By Fact 1, the prefix of length hh of SiS_i is the string at PP, and no earlier suffix shares more than hh symbols with SiS_i. So in Ti+1\mathcal{T}_{i+1}, SiS_i leaves the old tree at PP: if PP is a node, the leaf of SiS_i becomes a new child of it; if PP lies inside an edge, the edge is split by a new internal node at PP, whose children are the lower part of the edge and the new leaf. In both cases the next symbol of SiS_i after PP is larger than that of Si−1S_{i-1}, since Si−1<SiS_{i-1} < S_i, and larger than the next symbol of every earlier suffix through PP, since these suffixes are at most Si−1S_{i-1} and share the string at PP; so the new leaf is the last child of the node at PP.

Line L3 pops exactly the nodes of the path with depth greater than hh. If the new top has depth hh, it is the node at PP, and line L5 appends the leaf as its last child. Otherwise PP lies on the edge from the top, of depth below hh, to lastlast, the first popped node below it on the path, which is the last child of the top since the path is the rightmost one. Line L4 creates the node at PP with depth hh, puts it in place of lastlast, and makes lastlast its only child for now; its start is that of lastlast, which is a suffix below PP, so the labels of the two new edges are the two parts of the old label. Then line L5 appends the leaf. The depth of the leaf is ∣Si∣=n−SA[i]\lvert S_i \rvert = n - SA[i], and its start is SA[i]SA[i]. The children stay in order, and the stack now holds the path from the root to the new leaf: the nodes below PP that were not popped, the node at PP, and the leaf. So the invariant holds for i+1i + 1.

After the last iteration, the tree is Tn\mathcal{T}_n, the suffix tree of tt. Every internal node other than the root has at least two children: it was created at line L4 with lastlast as a child, and the leaf of line L5 was appended right away.

Time. Each iteration pushes at most two nodes (lines L4 and L5), so at most 2n2n pushes happen in total, and each iteration of the loop in line L3 pops one of them. All other work is constant per iteration. So the scan takes O(n)O(n) time, and it creates nn leaves and at most n−1n - 1 internal nodes. ■\blacksquare

Back to Suffix tree from suffix array