Algorithm Search⌘K

Proofs: Ukkonen's algorithm

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

Ukkonen's algorithm keeps the implicit suffix tree and runs in linear time full

This proof shows: correctness, termination, complexity.

Let t=t[0…N−1]t = t[0 \ldots N-1] with N=n+1N = n + 1 and t[n]=$t[n] = \$ not in t[0…n−1]t[0 \ldots n-1]. For j≥0j \ge 0 let Pj=t[0…j−1]P_j = t[0 \ldots j-1]. The implicit suffix tree Ij\mathcal{I}_j is the compact trie of the suffixes of PjP_j: the trie of these strings with every node removed that has one child and is neither the root nor the end of a string that is not a proper prefix of another one. A suffix of PjP_j is open if it is not a proper prefix of another substring of PjP_j that extends it, that is, if it occurs in PjP_j only as a suffix; open suffixes are exactly those that end at leaves.

Lemma 1 (suffix closure). If a string xx occurs in PjP_j, every suffix of xx occurs in PjP_j. If a suffix yy of PjP_j is not open (it occurs earlier too), then every shorter suffix of PjP_j is not open. Proof. The first is clear. For the second, yy occurs at some position other than the end, followed by a symbol; a suffix y′y' of yy occurs at the corresponding place, followed by the same symbol. □\square

So the suffixes of PjP_j split into open ones, the longest, and non-open ones, the shortest, including the empty suffix. Let rjr_j be the number of non-open nonempty suffixes; they are t[j−rj…j−1],…,t[j−1…j−1]t[j - r_j \ldots j-1], \ldots, t[j-1 \ldots j-1].

Lemma 2 (once a leaf, always a leaf). An open suffix t[i…j−1]t[i \ldots j-1] of PjP_j becomes t[i…j]t[i \ldots j], an open suffix of Pj+1P_{j+1}. Proof. If t[i…j]t[i \ldots j] occurred elsewhere in Pj+1P_{j+1}, then t[i…j−1]t[i \ldots j-1] would occur in PjP_j followed by a symbol. □\square

Lemma 3 (suffix links exist). In Ij\mathcal{I}_j, if cxcx labels an inner node other than the root, then xx labels an inner node or the root. Proof. cxcx is followed in PjP_j by two distinct symbols, and so is xx. □\square

Invariant. At the start of phase jj (line L3): (U1) the tree with open leaf edges ending at jj is Ij\mathcal{I}_j, and each leaf is labeled with the start of its suffix; (U2) remainder=rjremainder = r_j; (U3) the active point is the place of t[j−rj…j−1]t[j - r_j \ldots j-1] (the root if rj=0r_j = 0), and when length>0length > 0, t[edge…edge+length−1]t[edge \ldots edge + length - 1] is the string from nodenode to the active point; (U4) every inner node other than the root has a suffix link to the node of its label without the first symbol.

Initially j=0j = 0, the tree is the root, r0=0r_0 = 0, and all of (U1) to (U4) hold.

One phase. Let c=t[j]c = t[j]. Line L3 increases remainderremainder to rj+1r_j + 1, counting the new empty-plus-cc suffix: the strings to be extended by cc without a leaf are xk=t[j−k…j−1]x_k = t[j - k \ldots j-1] for k=rj,…,0k = r_j, \ldots, 0, and line L4 handles them from the longest. The open suffixes are extended by cc through their open leaf edges, without work, and stay open by Lemma 2.

Consider a pass with remainder=k+1remainder = k + 1, the active point at the place of xkx_k (by (U3), adjusted below). Lines L5 and L8 bring the active point to a canonical form, where lengthlength is less than the length of the edge it is on; line L8 is correct without reading symbols because, by (U3), xkx_k is spelled from the root, so the edge into ww matches the next ∣label(w)∣|label(w)| symbols of xkx_k.

  • If cc follows the place of xkx_k (line L9), then xkcx_k c occurs in PjP_j, so it is not open in Pj+1P_{j+1}, and by Lemma 1 none of xk−1c,…,x0cx_{k-1} c, \ldots, x_0 c is open either; all of them are spelled in the tree by Lemma 1. Nothing is added. The non-open suffixes of Pj+1P_{j+1} are xkc,…,x0cx_k c, \ldots, x_0 c, so rj+1=k+1=remainderr_{j+1} = k + 1 = remainder (U2), and length+1length + 1 moves the active point to the place of xkcx_k c (U3).
  • Otherwise xkcx_k c does not occur in PjP_j, so it is a new open suffix of Pj+1P_{j+1}, and it needs a leaf. If the place is a node, line L7 adds the leaf; if it is inside an edge, line L10 splits the edge at that place with a new node uu, whose label xkx_k is now followed by cc and by the old next symbol, so uu is a valid branching node, and adds the leaf. In both cases the trie stays compact. The leaf is for the suffix that starts at j−k=j−remainder+1j - k = j - remainder + 1.

After line L11, remainder=kremainder = k, and the next string to handle is xk−1x_{k-1}, which is xkx_k without its first symbol. If nodenode is the root, its place is found by dropping the first symbol of the string t[edge…edge+length−1]t[edge \ldots edge + length - 1] from the root, which line L12 does (the new string starts at j−remainder+1=j−k+1j - remainder + 1 = j - k + 1 and has length length−1length - 1). Otherwise nodenode is an inner node whose label is a prefix cycy of xkx_k, and by (U4) its link is the node of yy; the active point (link(node),edge,length)(link(node), edge, length) then spells yy followed by the same lengthlength symbols, which is xk−1x_{k-1}. If nodenode was the root and length=0length = 0, then xkx_k was empty (k=0k = 0) and the loop ends.

If the loop ends because remainder=0remainder = 0, then all of xrjc,…,x0cx_{r_j} c, \ldots, x_0 c became leaves, every suffix of Pj+1P_{j+1} is open except the empty one, rj+1=0r_{j+1} = 0, and the active point is the root.

Suffix links (U4). A node uu created in line L10 for xkx_k is an inner node of Pj+1P_{j+1} with label xkx_k, ∣xk∣≥1|x_k| \ge 1. If ∣xk∣=1|x_k| = 1, its link is the root, which line L12 then reaches: the next pass is at the root and either adds a leaf at the root (line L7, which sets link(u)←link(u) \leftarrow root) or stops by rule 3 at the root (line L9, same). If ∣xk∣≥2|x_k| \ge 2, the next pass handles xk−1x_{k-1}, the label of uu without its first symbol: it either finds a node there (rule 3 at a node, or rule 2 at a node, line L7), or creates one by a split (line L10); in each case it sets link(u)link(u) to that node, which is the node of xk−1x_{k-1}, as Lemma 3 requires. The pass handles the place of xk−1x_{k-1} exactly, so it cannot be a different node. So at the end of the phase every new inner node has its link, except possibly the last one, whose next pass must exist: the last node is created at remainder≥2remainder \ge 2 (at remainder=1remainder = 1 the active point is the root, x0x_0 is empty, and line L7 runs, not line L10), so one more pass follows in the same phase.

End. After phase nn, with the sentinel, no nonempty suffix of tt occurs twice, since each ends with the only $\$. So rN=0r_N = 0, all NN nonempty suffixes are open and end at leaves, and IN\mathcal{I}_N is the suffix tree of tt.

Time. Each pass of the inner loop either breaks (once per phase, at most NN times) or creates a leaf. Leaves are never removed (Lemma 2), and there are at most NN of them, so there are at most 2N2N passes. Define the node depth d(v)d(v) as the number of nodes on the path from the root to vv, the root excluded. For an inner node vv with label cycy, each ancestor a≠a \ne root of vv has label cy′cy' with y′y' a prefix of yy, and its link, the node of y′y', is an ancestor of the node of yy or the root, distinct for distinct aa (the labels y′y' differ), except that the ancestor with label cc links to the root. So d(link(v))≥d(v)−1d(link(v)) \ge d(v) - 1. Line L13 lowers the active node depth by at most 1, line L12 does not change nodenode, a split changes no node depth of the active node, and each step of line L8 raises it by 1. The node depth is at most NN at all times, starts at 0, and drops by at most 11 in each of the at most 2N2N passes, so the number of steps of line L8 is at most N+2N=3NN + 2N = 3N. Each step takes O(1)O(1) time with constant-time child lookup. So the total time is O(N)=O(n)O(N) = O(n), and the algorithm terminates. ■\blacksquare

Back to Ukkonen's algorithm