Algorithm Search⌘K

Proofs: Farach's algorithm

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

Farach's algorithm computes the suffix array and the LCP array in linear time full

This proof shows: correctness, termination, time.worst.

Notation. T′=T′[0…N−1]T' = T'[0 \ldots N-1] with symbols in {1,…,σ}\{1, \ldots, \sigma\} at positions 00 to N−2N - 2 and T′[N−1]=0T'[N-1] = 0, the sentinel. Positions are 0-based, Tp′=T′[p…N−1]T'_p = T'[p \ldots N-1], and the order is lexicographic. Since the sentinel is unique and smallest, two different suffixes are never prefixes of each other: for x≠yx \ne y, with h=lcp(Tx′,Ty′)h = \mathrm{lcp}(T'_x, T'_y), both x+hx + h and y+hy + h are less than NN and T′[x+h]≠T′[y+h]T'[x + h] \ne T'[y + h]. We write lcp(x,y)\mathrm{lcp}(x, y) for lcp(Tx′,Ty′)\mathrm{lcp}(T'_x, T'_y). A position is odd or even by its 0-based value. The proof is by induction on NN; for N≤4N \le 4, line L1 is direct.

Lemma 1 (odd suffixes). After L4, SAoSA_o lists the odd positions in suffix order, and LCPo[k]=lcp(SAo[k−1],SAo[k])LCP_o[k] = \mathrm{lcp}(SA_o[k-1], SA_o[k]).

Proof. Let m=⌊N/2⌋m = \lfloor N/2 \rfloor be the number of odd positions, and for 0≤i<m0 \le i < m let πi=(T′[2i+1],T′[2i+2])\pi_i = (T'[2i+1], T'[2i+2]), with T′[N]=0T'[N] = 0 as a pad. The pair πm−1\pi_{m-1} contains T′[N−1]=0T'[N-1] = 0 at its first or second place, and no other pair contains a 0, since 2i+2≤N−22i + 2 \le N - 2 for i<m−1i < m - 1. Line L2 sets R[i]R[i] to the rank of πi\pi_i, so R[i]<R[j]R[i] < R[j] if and only if πi<πj\pi_i < \pi_j, and R[m−1]R[m-1] is unique. Read two symbols at a time, T2i+1′T'_{2i+1} is πiπi+1⋯πm−1\pi_i \pi_{i+1} \cdots \pi_{m-1}, followed by the pad. Two such sequences for i≠ji \ne j differ at some pair before either ends, because the unique pair πm−1\pi_{m-1} can only be matched by itself, at the same offset from the end, which would mean i=ji = j. So comparing them pair by pair is comparing the suffixes of RR at ii and jj with the extra 0 of L3 never reached, and it is also comparing T2i+1′T'_{2i+1} and T2j+1′T'_{2j+1}, since pairs compare as their two symbols. By induction (∣R∣+1=m+1<N|R| + 1 = m + 1 < N for N>4N > 4), SARSA_R is the suffix array of R0R0, whose rank 0 is the suffix 00 at index mm; dropping it and mapping ii to 2i+12i + 1 gives SAoSA_o.

For the LCP, let x=SAo[k−1]x = SA_o[k-1] and y=SAo[k]y = SA_o[k], which are the suffixes of RR at ranks kk and k+1k + 1 of SARSA_R, so h=LCPR[k+1]h = LCP_R[k+1] is the number of equal leading pairs, and by the argument above the first pair that differs comes no later than πm−1\pi_{m-1} in either sequence. So x+2hx + 2h and y+2hy + 2h are the starts of the first pairs that differ, both less than NN. If T′[x+2h]≠T′[y+2h]T'[x + 2h] \ne T'[y + 2h], the lcp is 2h2h. Otherwise the second symbols differ, and the lcp is 2h+12h + 1. □\square

Lemma 2 (even suffixes). After L6, SAeSA_e lists the even positions in suffix order, and LCPe[k]=lcp(SAe[k−1],SAe[k])LCP_e[k] = \mathrm{lcp}(SA_e[k-1], SA_e[k]).

Proof. For an even p<N−1p < N - 1, Tp′=T′[p] Tp+1′T'_p = T'[p]\,T'_{p+1} with p+1p + 1 odd and less than NN. So for two such positions, Tp′<Tq′T'_p < T'_q if and only if T′[p]<T′[q]T'[p] < T'[q], or T′[p]=T′[q]T'[p] = T'[q] and Tp+1′<Tq+1′T'_{p+1} < T'_{q+1}, that is, the rank of p+1p + 1 in SAoSA_o is smaller (Lemma 1). If N−1N - 1 is even, its suffix is the sentinel alone, the smallest suffix, and its key (0,−1)(0, -1) is the smallest key. So sorting by the key of L5 sorts the even suffixes. For two adjacent even suffixes aa and bb: if T′[a]≠T′[b]T'[a] \ne T'[b], their lcp is 0; otherwise both symbols are nonzero, since the sentinel is unique, and lcp(a,b)=1+lcp(a+1,b+1)\mathrm{lcp}(a, b) = 1 + \mathrm{lcp}(a + 1, b + 1), which is the minimum of LCPoLCP_o over the ranks after the smaller rank up to the larger rank by the LCP-RMQ lemma. □\square

The trees and MM. To\mathcal{T}_o and Te\mathcal{T}_e are the compacted tries of the odd and the even suffixes; with distinct suffixes and no suffix a prefix of another, each suffix is a leaf. A point of a tree is a node or a place inside an edge; it spells a string, its path. The overmerge (L12 to L15) builds MM from pairs of points: the root of MM is paired with the two roots. At a node vv of MM paired with the points sos_o and ses_e (either may be missing), the edges out of sos_o and ses_e are grouped by first symbol. For each symbol cc, a child ww of depth dd is made, where dd is the smaller depth of the targets of the edges with symbol cc (of one or both trees), and ww is paired with the points at depth dd on these edges. A leaf of a tree whose depth is dd makes ww a leaf of MM for that suffix. Let Po(w)P_o(w) and Pe(w)P_e(w) be the paths of the points paired with ww. By induction from the root:

  • (a) the depth of ww in MM is ∣Po(w)∣|P_o(w)| and ∣Pe(w)∣|P_e(w)|, for each point that exists;
  • (b) the odd leaves below ww in MM (with ww itself if it is an odd leaf) are exactly the odd leaves below the point of ww in To\mathcal{T}_o, and in the same way for the even leaves;
  • (c) the children of ww have different first symbols cc, and cc is the true symbol at offset depth(w)\mathrm{depth}(w) of every suffix below that child;
  • (d) every node of To\mathcal{T}_o is paired with some node of MM, and so is every node of Te\mathcal{T}_e.

Point (d) holds because dd is never larger than the depth of either target, so the walk along an edge stops at its lower end. Each node of MM other than the root is at the lower end of an edge of one of the trees, so it is paired with a node of that tree, and each node of a tree is paired with only one node of MM, since the walk goes down. So MM has at most ∣To∣+∣Te∣=O(N)|\mathcal{T}_o| + |\mathcal{T}_e| = O(N) nodes, and the walk ends.

Lemma 3 (same parity). For two odd positions x≠x′x \ne x', lcp(x,x′)=depthM(LCAM(x,x′))\mathrm{lcp}(x, x') = \mathrm{depth}_M(\mathrm{LCA}_M(x, x')). The same holds for two even positions.

Proof. Let uu be the LCA of xx and x′x' in To\mathcal{T}_o; its string depth is lcp(x,x′)\mathrm{lcp}(x, x'). By (d), uu is paired with a node ww of MM, and by (b) both leaves are below ww. In To\mathcal{T}_o, the two leaves are in different subtrees of uu, whose edges have different first symbols, so by (c) they go to different children of ww, or one of them is ww. Hence ww is their LCA in MM, and by (a) its depth is lcp(x,x′)\mathrm{lcp}(x, x'). □\square

Lemma 4 (different parity). For a node vv of MM with both points, let λ(v)=lcp(Po(v),Pe(v))\lambda(v) = \mathrm{lcp}(P_o(v), P_e(v)), at most depth(v)\mathrm{depth}(v). For an odd xx and an even yy with LCAM(x,y)=v\mathrm{LCA}_M(x, y) = v, lcp(x,y)=λ(v)\mathrm{lcp}(x, y) = \lambda(v). Moreover λ(v)=0\lambda(v) = 0 if and only if vv is the root.

Proof. By (b), xx starts with Po(v)P_o(v) and yy with Pe(v)P_e(v). If λ(v)<depth(v)\lambda(v) < \mathrm{depth}(v), the two paths differ at offset λ(v)\lambda(v), so the suffixes do too, and lcp(x,y)=λ(v)\mathrm{lcp}(x, y) = \lambda(v). If Po(v)=Pe(v)P_o(v) = P_e(v), the suffixes share depth(v)\mathrm{depth}(v) symbols. Neither of them can be vv itself as a leaf: a leaf suffix ends with the sentinel at offset depth(v)−1\mathrm{depth}(v) - 1, and the other suffix would hold the same sentinel at the same offset, so it would be the same suffix. So they go to different children of vv, whose first symbols differ by (c), and lcp(x,y)=depth(v)=λ(v)\mathrm{lcp}(x, y) = \mathrm{depth}(v) = \lambda(v). At the root, both paths are empty and λ=0\lambda = 0. At any other node, the first symbol of the path is the first symbol of the child of the root that holds vv, which is a true symbol of both paths by (c), so λ(v)≥1\lambda(v) \ge 1. □\square

Lemma 5 (d-links). Lines L16 to L18 return lcp(x,y)\mathrm{lcp}(x, y) for xx and yy of different parity.

Proof. Let v=LCAM(x,y)v = \mathrm{LCA}_M(x, y). If vv is the root, the answer 0 is right by Lemma 4. Otherwise λ(v)≥1\lambda(v) \ge 1, so T′[x]=T′[y]T'[x] = T'[y], both symbols are nonzero, x+1x + 1 and y+1y + 1 are positions of different parity, and lcp(x,y)=1+lcp(x+1,y+1)\mathrm{lcp}(x, y) = 1 + \mathrm{lcp}(x + 1, y + 1). Each step of the loop goes to a pair one position further, so it ends at the root after at most NN steps, or earlier at a stored value. By induction along the chain from its end, each value computed is lcp\mathrm{lcp} of its pair, and by Lemma 4 that value is λ(w)\lambda(w) for the node ww where it is stored. So a stored value is right for every later pair with the same LCA. □\square

Theorem 1 (correctness). FARACH returns the suffix array and the LCP array of T′T'.

Proof. By Lemmas 3 and 5, LCP(x,y)=lcp(x,y)\mathrm{LCP}(x, y) = \mathrm{lcp}(x, y) for any two positions. With h=lcp(x,y)h = \mathrm{lcp}(x, y), Tx′<Ty′T'_x < T'_y if and only if T′[x+h]<T′[y+h]T'[x + h] < T'[y + h]. So L9 merges two lists that are sorted by Lemmas 1 and 2 with a correct comparison, and the result lists all NN suffixes in order. L10 is correct by the same two lemmas. The recursion ends, since its input is shorter. □\square

Theorem 2 (time). On an integer alphabet with σ≤NO(1)\sigma \le N^{O(1)}, with radix sort and linear-time range minimum and LCA structures, FARACH takes O(N)O(N) time in the worst case.

Proof. Without the recursive call: L2 is a radix sort of mm pairs with O(1)O(1) passes; L4 is a scan; L5 is a stable counting sort by T′[p]T'[p] of the even positions taken in the order of SAoSA_o; L6 is O(N)O(N) range minimum queries after linear preprocessing; L7 is two stack scans; L8 is the overmerge, O(1)O(1) work for each node of MM when the children of each point are grouped by first symbol with one global radix sort of all edges, and then linear LCA preprocessing. In L9 and L10 there are O(N)O(N) LCP queries. Each query of a different parity costs O(1)O(1) plus one loop step for each node whose value is stored for the first time, and each node of MM is stored at most once, so the total is O(N)O(N). The recursive call is on m+1≤N/2+1m + 1 \le N/2 + 1 symbols, with names at most mm. So t(N)≤t(N/2+1)+cNt(N) \le t(N/2 + 1) + cN, which gives t(N)=O(N)t(N) = O(N), since the sizes form a geometric series. ■\blacksquare

Back to Farach's algorithm