Algorithm Search⌘K

Proofs: Suffix array

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

Prefix doubling sorts the suffixes, and the two searches find the range of a pattern full

This proof shows: correctness, termination.

Let S=S[0…n−1]S = S[0 \ldots n-1] with n≥1n \ge 1, Si=S[i…n−1]S_i = S[i \ldots n-1], and for ℓ≥0\ell \ge 0 let SiℓS_i^{\ell} be the prefix of length min⁡(ℓ,n−i)\min(\ell, n - i) of SiS_i. Strings are compared in lexicographic order, in which a proper prefix is smaller.

Lemma 1 (splitting). For k≥1k \ge 1 and positions i,ji, j: Si2k<Sj2kS_i^{2k} < S_j^{2k} if and only if Sik<SjkS_i^{k} < S_j^{k}, or Sik=SjkS_i^{k} = S_j^{k} and Si+kk<Sj+kkS_{i+k}^{k} < S_{j+k}^{k}, where a suffix that starts at nn or later is the empty string.

Proof. Si2kS_i^{2k} is the concatenation Sik⋅Si+kkS_i^{k} \cdot S_{i+k}^{k}: if n−i≥kn - i \ge k, the first part has length kk and the second is the rest, cut at length kk; if n−i<kn - i < k, the first part is all of SiS_i and the second is empty. If Sik≠SjkS_i^{k} \ne S_j^{k}, then either they differ at a position less than kk and inside both, which decides both comparisons the same way, or one of them is a proper prefix of the other. In the second case the shorter one, say SikS_i^k, has length less than kk, so it is all of SiS_i, and Si2k=SikS_i^{2k} = S_i^k is a proper prefix of Sj2kS_j^{2k} as well, so the order is again the same. If Sik=SjkS_i^{k} = S_j^{k}, both have the same length; if that length is less than kk then both second parts are empty and the strings are equal, which matches "Si+kk<Sj+kkS_{i+k}^k < S_{j+k}^k is false"; otherwise the comparison of the 2k2k-prefixes is the comparison of the second parts. □\square

Invariant (R). After line L2, and after line L6 in the round with value kk (writing K=2kK = 2k, or K=1K = 1 after line L2): for all i,ji, j, rank[i]<rank[j]rank[i] < rank[j] if and only if SiK<SjKS_i^K < S_j^K, and rank[i]=rank[j]rank[i] = rank[j] if and only if SiK=SjKS_i^K = S_j^K. Moreover the ranks are 0,1,…,d−10, 1, \ldots, d - 1, where dd is the number of distinct KK-prefixes, and after line L6 SASA lists the positions in non-decreasing order of rank.

Initialization. Line L2 ranks each position by its symbol among the sorted distinct symbols, which is the order of the 1-prefixes.

Maintenance. In the round with value kk, (R) holds with K=kK = k before line L4. The key of line L4 is (rank[i],rank[i+k])(rank[i], rank[i+k]) with −1-1 when i+k≥ni + k \ge n. The value −1-1 is below each rank, as the empty string is below each nonempty one. By (R) for K=kK = k and Lemma 1, key(i)<key(j)key(i) < key(j) in lexicographic order of pairs if and only if Si2k<Sj2kS_i^{2k} < S_j^{2k}, and key(i)=key(j)key(i) = key(j) if and only if Si2k=Sj2kS_i^{2k} = S_j^{2k}. Line L5 sorts by key, and line L6 gives rank 0 to the first position and adds 1 exactly when the key changes, so the new ranks are 0,…,d−10, \ldots, d-1 and satisfy (R) with K=2kK = 2k.

Termination and result. If line L7 sees rank[SA[n−1]]=n−1rank[SA[n-1]] = n - 1, the nn ranks are 0,…,n−10, \ldots, n-1, so the 2k2k-prefixes of the nn suffixes are pairwise distinct. Two suffixes whose 2k2k-prefixes differ compare like these prefixes (a common prefix of length 2k2k would make them equal), so SASA is in increasing order of the suffixes and rankrank is its inverse. The test holds at the latest when 2k≥n2k \ge n: then Si2k=SiS_i^{2k} = S_i for each ii, and different suffixes are different strings. Since kk doubles in line L8, there are at most ⌈lg⁡n⌉+1\lceil \lg n \rceil + 1 rounds, and the loop ends. □\square

Lemma 2 (monotone heads). For a pattern PP of length mm, let h(r)=SSA[r]mh(r) = S_{SA[r]}^{m}. Then h(0)≤h(1)≤⋯≤h(n−1)h(0) \le h(1) \le \cdots \le h(n-1), and the set of rr with h(r)=Ph(r) = P is { r:P is a prefix of SSA[r] }\{\, r : P \text{ is a prefix of } S_{SA[r]} \,\}.

Proof. Let x<yx < y be two strings and cut both to length mm. If xx and yy differ at a first position pp inside both, the cut strings are equal when p≥mp \ge m, and ordered like xx and yy otherwise. If xx is a proper prefix of yy, the cut of xx is a prefix of the cut of yy. In all cases the cuts satisfy ≤\le. The second statement holds because h(r)h(r) has length mm exactly when the suffix has length at least mm, and then h(r)=Ph(r) = P means that PP is a prefix of the suffix; a shorter h(r)h(r) is never equal to PP. □\square

Theorem. SA-BOUND(P,false)(P, \text{false}) returns the least rr with h(r)≥Ph(r) \ge P, and SA-BOUND(P,true)(P, \text{true}) the least rr with h(r)>Ph(r) > P (nn if there is none). The suffixes that start with PP are exactly those with ranks in [lo,hi)[lo, hi), where lolo and hihi are these two values.

Proof. The predicate q(r)q(r), "h(r)≥Ph(r) \ge P" or "h(r)>Ph(r) > P", is monotone in rr by Lemma 2: once true, it stays true. Lines L9 to L16 keep the invariant "q(r)q(r) is false for r<lor < lo, and true for r≥hir \ge hi with hi≤nhi \le n" (where q(n)q(n) is read as true). It holds at line L9. In a step, lo≤mid<hilo \le mid < hi; line L14 sets lo=mid+1lo = mid + 1 only when q(mid)q(mid) is false, so qq is false for all r≤midr \le mid by monotonicity; line L15 sets hi=midhi = mid only when q(mid)q(mid) is true. The length hi−lohi - lo drops by at least ⌈(hi−lo)/2⌉≥1\lceil (hi - lo)/2 \rceil \ge 1 in each step, so the loop ends with lo=hilo = hi, the least rr with q(r)q(r) true. By Lemma 2, the ranks with h(r)=Ph(r) = P are those with h(r)≥Ph(r) \ge P and not h(r)>Ph(r) > P, which form the interval [lo,hi)[lo, hi), and they are the ranks of the suffixes that start with PP. ■\blacksquare

Back to Suffix array