后缀数组是一种处理字符串的算法,一般用倍增实现。
基本定义
后缀是指从某个位置 i i i开始到整个串末尾结束的一个特殊子串。字符串 r r r的第 i i i个字符开始的后缀的表示为 s u f f i x ( i ) suffix(i) suffix(i),也就是 s u f f i x ( i ) = r [ i … l e n ( r ) − 1 ] suffix(i)=r[i\dots len(r)-1] suffix(i)=r[i…len(r)−1]
后缀数组 s a sa sa是一个一维数组,它保存 1 … n 1\dots n 1…n的某个排列 s a [ 1 ] , s a [ 2 ] , … , s a [ n ] sa[1],sa[2],\dots,sa[n] sa[1],sa[2],…,sa[n],并且保证 s u f f i x ( s a [ i ] ) < s u f f i x ( s a [ i + 1 ] ) , 1 ≤ i < n suffix(sa[i])<suffix(sa[i+1]),1\leq i<n suffix(sa[i])<suffix(sa[i+1]),1≤i<n。也就是将 S S S的 n n n个后缀从小到大排序后把排好序的后缀的开头位置一次放入 s a sa sa中。
名次数组 r a n k [ i ] rank[i] rank[i]保存的是 s u f f i x ( i ) suffix(i) suffix(i)在所有后缀中从小到大排列的名次。
举个例子:
在求出名次数组后,可以仅用
O
(
1
)
O(1)
O(1)的时间比较任意两个字符的大小
倍增算法
如何求出这两个数组呢?一般使用的是倍增算法。用倍增的方法对每个字符开始的长度为
2
k
2^k
2k的子串进行排序,求出
r
a
n
k
rank
rank数组。k从0开始,每次加1,当
2
k
2^k
2k大于
n
n
n以后,每个字符开始的长度为
2
k
2^k
2k的子串就相当于每个后缀。并且因为没有两个相同的字符串,所以
r
a
n
k
rank
rank中没有相同的值。
code
int n,m,r[maxn],wa[maxn],wb[maxn],wv[maxn],vs[maxn],sa[maxn];
bool cmp(int *rr,int a,int b,int l){
return rr[a]==rr[b]&&rr[a+l]==rr[b+l];
}
void dd(){
int i,j,p,*x=wa,*y=wb,*t;
memset(vs,0,sizeof(vs));
for(i=0;i<n;i++) ++vs[x[i]=r[i]];
for(i=1;i<m;i++) vs[i]+=vs[i-1];
for(i=n-1;i>=0;i--) sa[--vs[x[i]]]=i;
for(j=1,p=1;p<n;j*=2,m=p){
for(i=n-j;i<n;i++) y[p++]=i;
for(i=0;i<n;i++)
if(sa[i]>=j) y[p++]=sa[i]-j;
for(i=0;i<n;i++) wv[i]=x[y[i]];
memset(vs,0,sizeof(vs));
for(i=0;i<n;i++) ++vs[wv[i]];
for(i=1;i<m;i++) vs[i]+=vs[i-1];
for(i=n-1;i>=0;i--) sa[--vs[wv[i]]]=y[i];
for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++){
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
}
}
}
待排序的字符串转换为int类型存储在r数组中,从 r [ 0 ] r[0] r[0]到 r [ n − 1 ] r[n-1] r[n−1],长度为n,且范围在0和m之间。为了操作方便,规定除 r [ n − 1 ] r[n-1] r[n−1]外所有 r [ i ] r[i] r[i]都大于0, r [ n − 1 ] = 0 r[n-1]=0 r[n−1]=0。 s a sa sa数组从 s a [ 0 ] sa[0] sa[0]到 s a [ n − 1 ] sa[n-1] sa[n−1]。
最长公共前缀
h e i g h t [ i ] height[i] height[i]为 s u f f i x ( s a [ i − 1 ] ) suffix(sa[i-1]) suffix(sa[i−1])和 s u f f i x ( s a [ i ] ) suffix(sa[i]) suffix(sa[i])的最长公共前缀,也就是排名相邻的两个后缀的最长公共前缀。对于j和k,不妨设 r a n k [ j ] < r a n k [ k ] rank[j]<rank[k] rank[j]<rank[k],则有以下性质:
s u f f i x ( j ) suffix(j) suffix(j)和 s u f f i x ( k ) suffix(k) suffix(k)的最长公共前缀为 h e i g h t [ r a n k [ j ] + 1 ] , h e i g h t [ r a n k [ j ] + 2 ] , … h e i g h t [ r a n k [ k ] ] height[rank[j]+1],height[rank[j]+2],\dots height[rank[k]] height[rank[j]+1],height[rank[j]+2],…height[rank[k]]的最小值
比如字符串 a a b a a a a b aabaaaab aabaaaab,求后缀 a b a a a a b abaaaab abaaaab和后缀 a a a b aaab aaab的最长公共前缀,如图所示:
但如何求height的值呢?
如果从1到n-1计算,最坏情况下要 O ( n 2 ) O(n^2) O(n2)。我们可以定义 h [ i ] = h e i g h t [ r a n k [ i ] ] h[i]=height[rank[i]] h[i]=height[rank[i]],也就是 s u f f i x ( i ) suffix(i) suffix(i)和在它前一名的后缀的最长公共前缀。
h数组满足 h [ i ] ≥ h [ i − 1 ] − 1 h[i]\geq h[i-1]-1 h[i]≥h[i−1]−1
证明:
设 s u f f i x ( k ) suffix(k) suffix(k)是排在 s u f f i x ( i − 1 ) suffix(i-1) suffix(i−1)前一名的后缀,则它们的最长公共前缀为 h [ i − 1 ] h[i-1] h[i−1]。那么 s u f f i x ( k + 1 ) suffix(k+1) suffix(k+1)将排在 s u f f i x ( i ) suffix(i) suffix(i)前面(这里要求 h [ i − 1 ] > 1 h[i-1]>1 h[i−1]>1,如果 h [ i − 1 ] ≤ 1 h[i-1]\leq 1 h[i−1]≤1,原式显然成立)并且 s u f f i x ( k + 1 ) suffix(k+1) suffix(k+1)和 s u f f i x ( i ) suffix(i) suffix(i)的最长公共前缀是 h [ i − 1 ] − 1 h[i-1]-1 h[i−1]−1,所以 s u f f i x ( i ) suffix(i) suffix(i)和它前一名的后缀的最长公共前缀至少为 h [ i − 1 ] − 1 h[i-1]-1 h[i−1]−1。按照h从1到n的顺序计算,并利用h数组的性质,时间复杂度可降为 O ( n ) O(n) O(n)
实现的时候没有必要保存h数组,只需按照 h [ 1 ] , h [ 2 ] , … h [ n − 1 ] h[1],h[2],\dots h[n-1] h[1],h[2],…h[n−1] 的顺序即可
code
void ch(){
int i,j,k=0;
for(i=0;i<n;i++) rk[sa[i]]=i;
for(i=0;i<n;h[rk[i++]]=k)
for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
}
代码中的h数组指 h e i g h t height height数组
最长公共子串
公共子串:如果字符串L同时出现在字符串A和字符串B中,则称字符串L是字符串A和字符串B的公共子串
字符串的任何一个子串都是其某个后缀的前缀,求两个字符串的最长公共子串就是求两个字符串的后缀的最长公共前缀。如果枚举两个字符串的后缀,则时间复杂度为 O ( n 2 ) O(n^2) O(n2)。我们可以将一个字符串并在另一个字符串后面,中间用一个没有出现过的字符隔开,再求新字符串的后缀数组。
以
A
=
a
a
a
b
a
,
B
=
a
b
a
a
A=aaaba,B=abaa
A=aaaba,B=abaa 为例,如图所示:
因为最长公共子串为A和B的后缀的最长公共前缀,所以最长公共子串的长度就是满足条件的
h
e
i
g
h
t
height
height值中的最大值。可取的
h
e
i
g
h
t
height
height要满足什么条件呢?如果两个后缀在原来的同一个字符串,则不能满足条件。所以只有当
s
u
f
f
i
x
(
s
a
[
i
−
1
]
)
suffix(sa[i-1])
suffix(sa[i−1])和
s
u
f
f
i
x
(
s
a
[
i
]
)
suffix(sa[i])
suffix(sa[i])不是同一个字符串的两个后缀时,
h
e
i
g
h
t
height
height值才是满足条件的。记
A
A
A和
B
B
B的长度为
∣
A
∣
|A|
∣A∣和
∣
B
∣
|B|
∣B∣,则时间复杂度为
O
(
∣
A
∣
+
∣
B
∣
)
O(|A|+|B|)
O(∣A∣+∣B∣)。