Algorithm Search⌘K

Proofs: Enhanced suffix array

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

The child table scans compute up, down and next, and give the child intervals full

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

Setting. L[0…N]L[0 \ldots N] has L[0]=0L[0] = 0, L[r]≥0L[r] \ge 0 for 1≤r<N1 \le r < N and L[N]=−1L[N] = -1. The fields upup, downdown and nextnext are defined as on the page. Write min⁡L(a,b)\min L(a, b) for min⁡{L[r]:a<r<b}\min\{L[r] : a < r < b\}, with +∞+\infty for an empty range. So up[q]up[q] is the smallest k<qk < q with L[k]>L[q]L[k] > L[q] and min⁡L(k,q)≥L[k]\min L(k, q) \ge L[k]; down[p]down[p] is the largest k>pk > p with L[k]>L[p]L[k] > L[p] and min⁡L(p,k)>L[k]\min L(p, k) > L[k]; next[i]next[i] is the smallest k>ik > i with L[k]=L[i]L[k] = L[i] and min⁡L(i,k)>L[i]\min L(i, k) > L[i].

Lemma 1 (first stack). In the first scan, just before rank ii is processed, the stack holds, from the bottom, exactly the ranks k<ik < i with min⁡L(k,i)≥L[k]\min L(k, i) \ge L[k], that is, the ranks with no smaller value after them before ii, in increasing order of rank. Their values do not decrease from the bottom.

Proof. Induction on ii. For i=1i = 1 the stack is [0][0] and the claim holds. Suppose it holds for ii. Rank kk stays after step ii if and only if L[k]≤L[i]L[k] \le L[i] (the pops remove exactly the top ranks with L[k]>L[i]L[k] > L[i], and since the values do not decrease from the bottom, these are all the ranks on the stack with a larger value), and then ii is pushed. So after step ii the stack holds the ranks k<ik < i with min⁡L(k,i)≥L[k]\min L(k, i) \ge L[k] and L[i]≥L[k]L[i] \ge L[k], and ii itself, which is the claim for i+1i + 1. □\square

Lemma 2 (up). The first scan sets up[i]up[i] correctly for every ii.

Proof. By Lemma 1, the candidates k<ik < i with L[k]>L[i]L[k] > L[i] and min⁡L(k,i)≥L[k]\min L(k, i) \ge L[k] are exactly the stack ranks with a value above L[i]L[i]. They are the top part of the stack and are popped at step ii, the smallest rank last. Line L6 stores the last one popped, so up[i]up[i] is the smallest candidate. If no rank is popped there is no candidate, and up[i]up[i] stays nil; the variable lastlast is reset after each use, so a pop at an earlier step does not leak. □\square

Lemma 3 (down). The first scan sets down[p]down[p] correctly for every pp.

Proof. Call kk a candidate for pp if k>pk > p, L[k]>L[p]L[k] > L[p] and min⁡L(p,k)>L[k]\min L(p, k) > L[k].

First, a write down[p]←lastdown[p] \gets last in L5 is a write of a candidate. At that point lastlast was directly above pp on the stack, so L[last]≥L[p]L[last] \ge L[p], and the test L[p]≠L[last]L[p] \ne L[last] gives L[last]>L[p]L[last] > L[p]. Take a rank rr with p<r<lastp < r < last and suppose L[r]≤L[last]L[r] \le L[last]. If rr was popped before step lastlast, it was popped by a rank ss with r<s<lastr < s < last and L[s]<L[r]≤L[last]L[s] < L[r] \le L[last]; repeat the argument with ss. This ends with a rank r∗r^* between pp and lastlast, with L[r∗]≤L[last]L[r^*] \le L[last], that is still on the stack at step lastlast. Step lastlast does not pop it, so it lies between pp and lastlast on the stack, which is false. So every value between pp and lastlast is larger than L[last]L[last], and lastlast is a candidate.

Second, every candidate kk is pushed directly above pp: all values between them are larger than L[k]>L[p]L[k] > L[p], so pp stays on the stack, and step kk pops every rank between them. If k<k′k < k' are candidates, then L[k]>L[k′]L[k] > L[k'], so step k′k' pops kk, with L[i]=L[k′]>L[p]L[i] = L[k'] > L[p], and L5 does not write. Let k∗k^* be the largest candidate. It is popped at some step ii, since L[N]=−1L[N] = -1. Every rank between k∗k^* and ii has a value of at least L[k∗]L[k^*], or it would have popped k∗k^* earlier. If L[i]>L[p]L[i] > L[p], then ii would be a candidate larger than k∗k^*, so L[i]≤L[p]L[i] \le L[p]. After the pop, pp is the top and L[p]≠L[k∗]L[p] \ne L[k^*], so L5 writes down[p]←k∗down[p] \gets k^*. By the first part no other write to down[p]down[p] happens after this one, since pp has no candidate after k∗k^*, and if pp has no candidate, nothing is written. □\square

Lemma 4 (next). The second scan sets next[i]next[i] correctly for every ii.

Proof. As in Lemma 1, with one change: a rank with a value equal to L[i]L[i] is popped by line L11 and replaced by ii. So before step ii, the stack holds the ranks k<ik < i with min⁡L(k,i)>L[k]\min L(k, i) > L[k], with strictly increasing values. At step ii, after the pops of larger values, the top has L[top]≤L[i]L[top] \le L[i]. If L[top]=L[i]L[top] = L[i], the top kk satisfies min⁡L(k,i)>L[k]=L[i]\min L(k, i) > L[k] = L[i], and it is the only such rank with this value, so next[k]=inext[k] = i, and no smaller ii was possible since kk was not popped before. If no top has the value L[i]L[i], no earlier rank has next=inext = i. Each rank is set at most once, when it is popped by an equal value; it is popped by a smaller value otherwise and keeps nil, which is right since every later rank with an equal value has the smaller value in between. □\square

Theorem (children). Let [i..j][i..j] be an ℓ\ell-interval with ℓ\ell-indices k1<⋯<krk_1 < \cdots < k_r. Then ESA-CHILDREN emits [i..k1−1],[k1..k2−1],…,[kr..j][i..k_1 - 1], [k_1..k_2 - 1], \ldots, [k_r..j].

Proof. The ℓ\ell-indices are the ranks in i+1…ji+1 \ldots j with L=ℓL = \ell, and all values there are at least ℓ\ell. For t<rt < r, next[kt]=kt+1next[k_t] = k_{t+1}, since all values between are larger than ℓ\ell; and next[kr]next[k_r] is nil, since L[j+1]<ℓL[j+1] < \ell lies between krk_r and any later rank with the value ℓ\ell. So L16 and L17 emit the right intervals once k=k1k = k_1.

For the root [0..n][0..n], ℓ=0=L[0]\ell = 0 = L[0], and k1=next[0]k_1 = next[0] is the first rank after 0 with L=0L = 0; the chain ends because L[N]=−1L[N] = -1.

Otherwise i≥1i \ge 1 or j<nj < n, and L[i]<ℓL[i] < \ell, L[j+1]<ℓL[j+1] < \ell. If L[i]≤L[j+1]L[i] \le L[j+1], then L[j+1]<ℓL[j+1] < \ell, and the smallest k<j+1k < j + 1 with L[k]>L[j+1]L[k] > L[j+1] and min⁡L(k,j+1)≥L[k]\min L(k, j+1) \ge L[k] is k1k_1: it qualifies since everything between k1k_1 and j+1j + 1 is at least ℓ\ell; every rank in i+1…k1−1i + 1 \ldots k_1 - 1 has a value above ℓ\ell and then k1k_1, with value ℓ\ell, lies between it and j+1j + 1; and rank ii and those before it have L[i]≤L[j+1]L[i] \le L[j+1] between or on them. So up[j+1]=k1up[j+1] = k_1, which lies in i+1…ji + 1 \ldots j. If L[i]>L[j+1]L[i] > L[j+1], then rank j+1j + 1 has the smaller value L[j+1]<L[i]L[j+1] < L[i], and up[j+1]up[j+1], if defined, is at most ii: rank ii itself qualifies as a candidate when min⁡L(i,j+1)≥L[i]\min L(i, j+1) \ge L[i], which holds as all values there are at least ℓ>L[i]\ell > L[i]. So the test in L14 fails and the algorithm uses down[i]down[i]. By definition, down[i]down[i] is the largest k>ik > i with L[k]>L[i]L[k] > L[i] and only larger values between ii and kk. The rank k1k_1 qualifies. A rank k>k1k > k_1 inside the interval has k1k_1 between with ℓ≤L[k]\ell \le L[k], so it fails, and a rank beyond jj has L[j+1]<ℓL[j+1] < \ell, so either L[j+1]≤L[i]L[j+1] \le L[i] blocks it or j+1j+1 itself fails when L[j+1]<L[i]L[j+1] < L[i], and later ranks have j+1j + 1 between. So down[i]=k1down[i] = k_1.

The case L[i]≤L[j+1]L[i] \le L[j+1] also needs up[j+1]>iup[j+1] > i: any k≤ik \le i has rank ii or a smaller value between, and L[i]≤L[j+1]L[i] \le L[j+1] is not larger than L[j+1]L[j+1], so k=ik = i fails the first condition, and a k<ik < i with L[k]>L[j+1]≥L[i]L[k] > L[j+1] \ge L[i] fails the second, since L[i]<L[k]L[i] < L[k] lies between. □\square

Time and termination. Each scan pushes each rank once and pops it at most once, so each takes O(N)O(N) time and ends. ESA-CHILDREN takes O(1)O(1) time per child, since every nextnext step moves to a larger rank inside [i..j][i..j]. Find takes O(σ)O(\sigma) time per node for the child scan, at most m+1m + 1 nodes, and O(m)O(m) symbol comparisons in all, so O(mσ)O(m\sigma). ■\blacksquare

Back to Enhanced suffix array