Algorithm Search⌘K

Proofs: Suffix array pattern search

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

SA-BOUND returns the lower bound or the upper bound of the pattern full

This proof shows: correctness, termination, complexity.

Let S=S[0…n−1]S = S[0 \ldots n-1], SASA its suffix array, P=P[0…m−1]P = P[0 \ldots m-1] with m≥0m \ge 0. Write Tq=SSA[q]T_q = S_{SA[q]} for the suffix of rank qq and TqmT_q^m for its prefix of length min⁡(m,∣Tq∣)\min(m, |T_q|). Add T−1T_{-1}, smaller than every string, and TnT_n, larger than every string, with lcp(T−1,x)=lcp(Tn,x)=0lcp(T_{-1}, x) = lcp(T_n, x) = 0 for each xx. Write lcp(a,b)lcp(a, b) for the length of the longest common prefix of TaT_a and TbT_b.

Call rank qq low if Tqm<PT_q^m < P (non-strict search) or Tqm≤PT_q^m \le P (strict search). By Lemma 2 of the suffix array proof, the TqmT_q^m are sorted, so the low ranks are 0,…,b−10, \ldots, b - 1 for some bb, and bb is the lower bound (or the upper bound). Count −1-1 as low and nn as not low.

Fact A (order of sorted strings). For a<ca < c among −1,…,n-1, \ldots, n and d=lcp(a,c)d = lcp(a, c): either TaT_a has length dd, or both have a symbol at position dd and Ta[d]<Tc[d]T_a[d] < T_c[d] (with the virtual ranks this holds by convention). This is the definition of Ta<TcT_a < T_c.

Fact B. For a<c<ea < c < e, lcp(a,e)=min⁡(lcp(a,c),lcp(c,e))lcp(a, e) = \min(lcp(a, c), lcp(c, e)) when all three are real, and lcp(a,e)≤lcp(a,c)lcp(a, e) \le lcp(a, c), lcp(a,e)≤lcp(c,e)lcp(a, e) \le lcp(c, e) in all cases. This is the usual property of sorted strings: lcp(a,e)=min⁡(LCP[a+1],…,LCP[e])lcp(a, e) = \min(LCP[a+1], \ldots, LCP[e]).

Invariant. At each test of line L3: lolo is low, hihi is not low, lo<hilo < hi, l=lcp(P,Tlo)l = lcp(P, T_{lo}) and r=lcp(P,Thi)r = lcp(P, T_{hi}), where lcp(P,x)lcp(P, x) is the length of the longest common prefix of PP and xx. Also Llcp[mid]=lcp(lo,mid)Llcp[mid] = lcp(lo, mid) and Rlcp[mid]=lcp(mid,hi)Rlcp[mid] = lcp(mid, hi) for the midpoint computed in line L4. The last statement holds because (lo,hi)(lo, hi) is always one of the intervals of the fixed recursion (−1,n)→(lo,mid),(mid,hi)(-1, n) \to (lo, mid), (mid, hi) used to build the tables.

Initialization. lo=−1lo = -1 is low, hi=nhi = n is not, and l=r=0l = r = 0.

Maintenance. Let lo<mid<hilo < mid < hi.

Line L6: l≥rl \ge r and Llcp[mid]>lLlcp[mid] > l. Then TmidT_{mid} agrees with TloT_{lo} on the first l+1l + 1 symbols, so lo≠−1lo \ne -1 and TloT_{lo} has a symbol at position ll. If l=ml = m, then PP is a prefix of TloT_{lo}; since lolo is low, the search is strict, and PP is also a prefix of TmidT_{mid}, so midmid is low. If l<ml < m, then TloT_{lo} and PP differ at ll, and Tlo[l]<P[l]T_{lo}[l] < P[l] because lolo is low; Tmid[l]=Tlo[l]<P[l]T_{mid}[l] = T_{lo}[l] < P[l] and they agree before ll, so Tmidm<PT_{mid}^m < P and midmid is low, with lcp(P,Tmid)=llcp(P, T_{mid}) = l. In both cases lo←midlo \leftarrow mid keeps the invariant with the same ll.

Line L7: l≥rl \ge r and Llcp[mid]=d<lLlcp[mid] = d < l. Then lo≠−1lo \ne -1 (else l=0l = 0), and by Fact A at position dd either TloT_{lo} ends at dd, which is impossible since TloT_{lo} agrees with PP on l>dl > d symbols, or Tlo[d]<Tmid[d]T_{lo}[d] < T_{mid}[d] (or midmid is virtual, which it is not). Since Tlo[d]=P[d]T_{lo}[d] = P[d] and d<l≤md < l \le m, we get P[d]<Tmid[d]P[d] < T_{mid}[d] with agreement before dd. So Tmidm>PT_{mid}^m > P, midmid is not low in either search, and lcp(P,Tmid)=dlcp(P, T_{mid}) = d. Setting hi←midhi \leftarrow mid, r←dr \leftarrow d keeps the invariant.

Lines L9 and L10 are the mirror images, with ThiT_{hi}, Fact A for mid<himid < hi, and "not low" in place of "low". For L9, Rlcp[mid]>rRlcp[mid] > r means TmidT_{mid} agrees with ThiT_{hi} beyond position rr; if r=mr = m then PP is a prefix of ThiT_{hi}, the search is not strict, and PP is a prefix of TmidT_{mid}, so midmid is not low; if r<mr < m, ThiT_{hi} has a symbol at rr (else ThiT_{hi} would be a proper prefix of PP, hence low), Thi[r]>P[r]T_{hi}[r] > P[r], and the same holds for TmidT_{mid}. For L10, Rlcp[mid]=d<rRlcp[mid] = d < r gives Tmid[d]<Thi[d]=P[d]T_{mid}[d] < T_{hi}[d] = P[d] or TmidT_{mid} ending at dd, so midmid is low with lcp(P,Tmid)=dlcp(P, T_{mid}) = d.

Lines L8, L11, L12 to L15. If l≥rl \ge r and Llcp[mid]=lLlcp[mid] = l, then TmidT_{mid} agrees with TloT_{lo}, and so with PP, on the first ll symbols (if lo=−1lo = -1 then l=0l = 0 and there is nothing to show). The case l<rl < r, Rlcp[mid]=rRlcp[mid] = r is the same with hihi. So line L12 starts with a kk such that TmidT_{mid} and PP agree on kk symbols, and ends with k=lcp(P,Tmid)k = lcp(P, T_{mid}). Line L13 then decides lowness: if k=mk = m, Tmidm=PT_{mid}^m = P, low exactly in the strict search; if TmidT_{mid} ends at k<mk < m, Tmidm=TmidT_{mid}^m = T_{mid} is a proper prefix of PP, so low; otherwise the order of Tmid[k]T_{mid}[k] and P[k]P[k] decides. Lines L14 and L15 move lolo or hihi and store kk into ll or rr, which keeps the invariant.

Termination and result. Each pass replaces (lo,hi)(lo, hi) by (lo,mid)(lo, mid) or (mid,hi)(mid, hi) with lo<mid<hilo < mid < hi, so hi−lohi - lo decreases, and the loop ends with hi=lo+1hi = lo + 1. Then lolo is low and hihi is not; since the low ranks are −1,0,…,b−1-1, 0, \ldots, b - 1, hi=bhi = b. So line L16 returns the lower bound for the non-strict search and the upper bound for the strict one, and the suffixes that start with PP are exactly the ranks in [lb,ub)[lb, ub).

Comparison count. With ⌊(lo+hi)/2⌋\lfloor (lo + hi)/2 \rfloor, the new gap is at most ⌈(hi−lo)/2⌉\lceil (hi - lo)/2 \rceil, so from hi−lo=n+1hi - lo = n + 1 there are at most ⌈lg⁡(n+1)⌉\lceil \lg(n+1) \rceil passes. The value max⁡(l,r)\max(l, r) never decreases: lines L6 and L9 change neither; line L7 runs with l≥rl \ge r and sets rr below ll; line L10 runs with l<rl < r and sets ll below rr; lines L14 and L15 store the end of line L12, which started at max⁡(l,r)\max(l, r). Each successful equality test in line L12 makes kk, and then max⁡(l,r)\max(l, r), one larger than any earlier value, and max⁡(l,r)≤m\max(l, r) \le m, so the successful tests number at most mm over the whole search. Each pass has at most one failed equality test and at most one order test in line L13. So the search uses at most m+2⌈lg⁡(n+1)⌉m + 2 \lceil \lg(n+1) \rceil symbol comparisons. ■\blacksquare

Back to Suffix array pattern search