Algorithm Search⌘K

Proofs: SA-IS algorithm

These are the human proofs of SA-IS algorithm, at full length.

SA-IS returns the suffix array in linear time full

This proof shows: correctness, termination, complexity.

Let T=T[0…n]T = T[0 \ldots n] with T[n]=0T[n] = 0 and T[i]≥1T[i] \ge 1 for i<ni < n, so the sentinel is the unique smallest symbol. Write Ti=T[i…n]T_i = T[i \ldots n]. All suffixes are distinct, since they have distinct lengths and each contains the sentinel exactly once, at its end. The types are as defined on the page: nn is S-type, and for i<ni < n, ii is S-type if Ti<Ti+1T_i < T_{i+1} and L-type if Ti>Ti+1T_i > T_{i+1}.

Lemma 1 (type rule). For i<ni < n: if T[i]<T[i+1]T[i] < T[i+1] then ii is S-type; if T[i]>T[i+1]T[i] > T[i+1] then ii is L-type; if T[i]=T[i+1]T[i] = T[i+1] then ii has the type of i+1i+1.

Proof. The first two cases are decided by the first symbol. If T[i]=T[i+1]=cT[i] = T[i+1] = c, then Ti=c Ti+1T_i = c\,T_{i+1} and Ti+1=c Ti+2T_{i+1} = c\,T_{i+2}, so Ti<Ti+1T_i < T_{i+1} if and only if Ti+1<Ti+2T_{i+1} < T_{i+2}. For i+1=ni + 1 = n this case cannot happen, since T[n]T[n] is unique. □\square

So the right-to-left scan of line L1 computes the types.

Lemma 2 (L before S in a bucket). If T[i]=T[j]=cT[i] = T[j] = c, ii is L-type and jj is S-type, then Ti<TjT_i < T_j.

Proof. Let Ti=cad…T_i = c^a d \ldots with d≠cd \ne c the first symbol different from cc, a≥1a \ge 1. By Lemma 1 the type of ii is the type at the last cc of the run, which is L-type, so d<cd < c. In the same way Tj=cbe…T_j = c^b e \ldots with e>ce > c. If a≤ba \le b, position aa of the two suffixes has d<c≤d < c \le the symbol of TjT_j there (it is cc if a<ba < b and ee if a=ba = b). If a>ba > b, position bb has cc in TiT_i and e>ce > c in TjT_j. Either way Ti<TjT_i < T_j. □\square

So each bucket [head(c),tail(c))[\mathrm{head}(c), \mathrm{tail}(c)) of SASA consists of the L-type suffixes that start with cc, followed by the S-type suffixes that start with cc.

Lemma 3 (successor order). If T[i]=T[j]T[i] = T[j] and i,j<ni, j < n, then Ti<TjT_i < T_j if and only if Ti+1<Tj+1T_{i+1} < T_{j+1}.

Proof. Ti=T[i] Ti+1T_i = T[i]\,T_{i+1} and Tj=T[j] Tj+1T_j = T[j]\,T_{j+1} with equal first symbols. □\square

Lemma 4 (induced sorting of suffixes). Suppose SASA is empty except that the LMS positions are placed at the ends of their buckets, and within each bucket in increasing order of their suffixes. Then INDUCE-L followed by INDUCE-S leaves the suffix array of TT in SASA.

Proof. INDUCE-L. Every L-type jj has Tj+1<TjT_{j+1} < T_j, and j+1j + 1 is either L-type or S-type; if it is S-type it is LMS, since its left neighbor jj is L-type. We show by induction on the rank of TjT_j among the L-type suffixes that each L-type jj is written exactly once, at its final rank, and before the scan reaches that rank.

Take an L-type jj and assume the claim for all smaller L-type suffixes. Its successor j+1j + 1 is in SASA at its final rank before the scan reaches that rank: an LMS position was placed there at the start, and an L-type successor is smaller, so the induction applies. The L-type suffixes in bucket T[j]T[j] that are smaller than TjT_j have, by Lemma 3, smaller successors, so their successors are read before j+1j + 1 and they are written before jj; the larger ones have larger successors and are written after jj. So when the scan reads j+1j + 1, the head of bucket T[j]T[j] is exactly at the final rank of TjT_j, by Lemma 2, and jj is written there. This rank is larger than the rank of Tj+1T_{j+1}, since Tj>Tj+1T_j > T_{j+1}, so the scan has not yet passed it and will read jj later. Each entry is read once, so jj is written once. The LMS entries are not moved, and empty slots are skipped.

INDUCE-S. The scan goes right to left with fresh tail pointers, so the LMS entries placed before are overwritten as the S-type positions are written. Every S-type j<nj < n has Tj+1>TjT_{j+1} > T_j. By the symmetric induction, from the largest S-type suffix down: when the scan reads j+1j + 1, which is either L-type (already in its final place after INDUCE-L) or S-type and larger (already written by this scan, at a rank to the right), jj is written at the tail of bucket T[j]T[j]. By Lemma 3, among S-type positions in one bucket the larger suffix has the larger successor, which is read earlier, so the S-part of each bucket is filled from its end in decreasing order. The sentinel nn is alone in bucket 0. Every S-type position j<nj < n has a successor that is read, so all S-type positions are written. With Lemma 2 this gives every bucket in sorted order, and the buckets are in order of their first symbols. □\square

Lemma 5 (sorting the LMS substrings). After lines L3 to L5, the LMS positions appear in SASA in an order in which their LMS substrings are nondecreasing, where LMS substrings are compared symbol by symbol, and at an equal symbol by type with L before S, up to and including the next LMS position.

Proof. Run the argument of Lemma 4 with each suffix TiT_i replaced by its LMS prefix, the string of pairs (symbol, type) from ii to the first LMS position after ii, inclusive (for i=ni = n, just the sentinel). Lemmas 2 and 3 hold for LMS prefixes with this order: the type pair settles the case of Lemma 2, and for a non-LMS successor the LMS prefix of ii is T[i]T[i] followed by the LMS prefix of i+1i + 1. At the start, the LMS positions are at the ends of their buckets in some order; their LMS prefixes are compared only at the first pair inside a bucket, which is (c, S) for all of them, so any order is consistent with the LMS prefixes truncated to one symbol. The induction then shows that every L-type and S-type non-LMS position is placed in the order of its LMS prefix, and in INDUCE-S the LMS positions themselves are rewritten, each induced from its successor, in the order of its LMS substring. □\square

Lemma 6 (reduction). Let P=(p0<p1<⋯<pm−1=n)P = (p_0 < p_1 < \cdots < p_{m-1} = n) be the LMS positions and let T′[k]T'[k] be the name of the LMS substring at pkp_k given by line L6. Then Tpa<TpbT_{p_a} < T_{p_b} if and only if Ta′<Tb′T'_a < T'_b, where Ta′T'_a is the suffix of T′T' from aa.

Proof. By Lemma 5 and line L6, names are equal exactly for equal LMS substrings (as strings of pairs), and a smaller name means a smaller LMS substring. T′[m−1]=0T'[m-1] = 0 is the name of the sentinel substring, which is the smallest and unique, so T′T' again ends with a unique smallest symbol. Compare TpaT_{p_a} and TpbT_{p_b}: read their LMS substrings one after another. While the names agree, the substrings are equal, including the type of the last symbol, so the two suffixes continue from LMS positions pa+kp_{a+k} and pb+kp_{b+k}. At the first kk where the names differ, the LMS substrings differ. If they differ at a symbol, that symbol decides both comparisons in the same way. If they first differ in type at some offset with equal symbols, then the suffix with the L-type position is smaller there by the argument of Lemma 2 (applied to the two positions, which have the same symbol), and the L type is also smaller in the pair order. One LMS substring cannot be a proper prefix of the other as a string of pairs: if the shorter one ends at the LMS position qq, then qq is S-type and q−1q - 1 is L-type, and the other substring has the same pairs at the same offsets, so its position at that offset is also LMS and it ends there too. So the first difference of names gives the order of the suffixes, which is also the order of Ta′T'_a and Tb′T'_b. □\square

Theorem. SA-IS returns the suffix array of TT.

Proof. By strong induction on the length of TT. If all names are distinct (line L8), the order of the names is the order of the LMS substrings, which by Lemma 6 with distinct names is the order of the LMS suffixes. Otherwise, T′T' has length m≤⌈(n+1)/2⌉<n+1m \le \lceil (n+1)/2 \rceil < n + 1 for n≥2n \ge 2 (no two LMS positions are adjacent, and 0 is never LMS), its alphabet is {0,…,m−1}\{0, \ldots, m-1\}, and it ends with a unique smallest 0; by induction line L7 returns its suffix array, and by Lemma 6 this is the order of the LMS suffixes. Lines L9 and L10 then satisfy the hypothesis of Lemma 4, which gives the suffix array. For n≤1n \le 1 there is at most one LMS position and the names are distinct. □\square

Termination and time. Each call on a string of length NN with alphabet size KK does O(N+K)O(N + K) work outside the recursion: the type scan, four bucket computations, four induction scans and the placement loops are each linear. In the naming, the comparison of two neighboring LMS substrings runs over at most the length of the shorter one plus one; each position lies in at most two LMS substrings (its own and as the shared end of the previous one), and each substring is compared at most twice (with its left and right neighbor in SASA), so the naming is O(N)O(N). The recursive call is on m≤⌈N/2⌉m \le \lceil N/2 \rceil symbols with alphabet size at most mm. So W(N)≤W(⌈N/2⌉)+c(N+K)W(N) \le W(\lceil N/2 \rceil) + c(N + K), and with K=O(N)K = O(N) at the top and K≤NK \le N below, W(N)=O(N)W(N) = O(N). The length at least halves at each level and the recursion stops when the names are distinct, which happens at length 1 at the latest, so the depth is at most ⌈lg⁡(n+1)⌉\lceil \lg (n + 1) \rceil and the algorithm terminates. ■\blacksquare

Back to SA-IS algorithm