一、一些前置定义:
后缀数组主要指
s
a
sa
sa和
r
k
rk
rk这两个数组。
字符串下标从
1
1
1 开始。
“
s
u
f
[
i
]
suf[i]
suf[i]” (后缀
i
i
i )表示以第
i
i
i 个字符为开头的后缀。
s
a
[
i
]
sa[i]
sa[i] 表示排名为
i
i
i 的后缀的编号。
r
k
[
i
]
rk[i]
rk[i] 表示后缀
i
i
i 的排名。
二、后缀数组的求法:
1)纯暴力做法:
s
t
r
i
n
g
string
string+
s
o
r
t
sort
sort.
一次字符串比较的复杂度为
O
(
N
)
O(N)
O(N) ,因此该算法的复杂度为
O
(
n
2
l
o
g
n
)
O(n^2log n)
O(n2logn)。
2) O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)的做法
该做法运用了倍增的思想。
先从长度为
1
1
1的字符串开始比较,然后对每次比较的字符串长度进行倍增处理。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+60;
int sa[maxn<<1],rk[maxn<<1],oldrk[maxn<<1];
int w=1,n;
char s[maxn];
bool cmp(int x,int y)
{
return rk[x]==rk[y] ? rk[x+w]<rk[y+w] : rk[x]<rk[y];
}//以rk[x]为第一关键字,以rk[x+w]为第二关键字
int main()
{
scanf("%s",s+1);
n=strlen(s+1);
for(int i=1;i<=n;++i) sa[i]=i,rk[i]=s[i];
for(w=1;w<n;w<<=1)
{
sort(sa+1,sa+n+1,cmp);
memcpy(oldrk,rk,sizeof(rk));//因为rk的顺序会被改变,因此应当先把rk放到oldrk中
for(int i=1,p=0;i<=n;++i)
{
if(oldrk[sa[i]]==oldrk[sa[i-1]] && oldrk[sa[i]+w]==oldrk[sa[i-1]+w]) rk[sa[i]]=p;
else rk[sa[i]]=++p;
}//去重
}
for(int i=1;i<=n;++i) printf("%d ",sa[i]);
return 0;
}
让我们考虑以下这样做为什么是合理的。
手动模拟以上的过程即可。
例:对于字符串
a
b
a
b
abab
abab(fxj语十级)
s
u
f
[
1
]
=
a
b
a
b
,
s
u
f
[
2
]
=
b
a
b
,
s
u
f
[
3
]
=
a
b
,
s
u
f
[
4
]
=
b
suf[1]=abab,suf[2]=bab,suf[3]=ab,suf[4]=b
suf[1]=abab,suf[2]=bab,suf[3]=ab,suf[4]=b。
先对进行一次
w
=
1
w=1
w=1的排序。
得到从小到大的顺序
a
b
a
b
abab
abab,
a
b
ab
ab,
b
a
b
bab
bab,
b
b
b
在进行一次
w
=
2
w=2
w=2的排序。
得到从小到大的顺序
a
b
a
b
abab
abab,
a
b
ab
ab,
b
b
b,
b
a
b
bab
bab
再进行一次w=4的排序
得到从小到大的顺序
a
b
ab
ab,
a
b
a
b
abab
abab,
b
b
b,
b
a
b
bab
bab。
总结 :
倍增比较成立的根本原因是字符串比较的字典序中先比较前面的字符再比较后面的字符,最后比较长度。即如果一个字符串
a
a
a的前面任意一位z
i
i
i大于另一个字符串
b
b
b的相同位置,则字符串
a
a
a一定大于字符串
b
b
b,后面的剩余部分不需要比较。
3)优化做法:
该算法的瓶颈主要在于排序,因此优化排序就能突破这一瓶颈。
可以用基数排序和计数排序优化到
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn) 甚至是
O
(
n
)
O(n)
O(n)。
由于我并没有研究过基数排序因此这一优化将会推迟至我搞完基数排序
以及我觉得上面这个证明方式是真的太草率了因此一定会尽快补上严谨证明的!
三、 h e i g h t height height数组
1) L C P LCP LCP(最长公共前缀)
两个字符串
S
S
S 和
T
T
T就是最大的
x
x
x
(
x
≤
m
i
n
(
∣
S
∣
,
∣
T
∣
)
)
(x\le min(|S|,|T|))
(x≤min(∣S∣,∣T∣)),使得
S
i
S_i
Si
=
=
=
T
i
T_i
Ti
(
∀
1
≤
i
≤
x
)
\ (\forall\ 1\le i \le x)
(∀ 1≤i≤x)。
下文以
l
c
p
(
i
,
j
)
lcp(i,j)
lcp(i,j) 表示后缀
i
i
i 和 后缀
j
j
j 的最长公共前缀(的长度)。
2) h e i g h t height height数组的定义
h
e
i
g
h
t
[
i
]
height[i]
height[i]=
l
c
p
(
s
a
[
i
]
,
s
a
[
i
−
1
]
)
lcp(sa[i],sa[i-1])
lcp(sa[i],sa[i−1]),即第
i
i
i名的后缀与它前一名的后缀的最长公共前缀。
h
e
i
g
h
t
[
i
]
=
0
height[i] = 0
height[i]=0。(可以看作一个规定,实际上并不存在
h
e
i
g
h
t
[
1
]
height[1]
height[1])。
3) O ( n ) O(n) O(n)求 h e i g h t height height数组需要的一个引理
h e i g h t [ r k [ i ] ] ≥ h e i g h t [ r k [ i − 1 ] ] − 1 height[rk[i]] \ge height[rk[i-1]]-1 height[rk[i]]≥height[rk[i−1]]−1。
翻译一下,后缀 i i i 与后缀 s a [ r k [ i ] − 1 ] sa[rk[i]-1] sa[rk[i]−1] 的最长公共前缀长度大于等于后缀 s a [ r k [ i − 1 ] ] sa[rk[i-1]] sa[rk[i−1]] 与后缀 s a [ r k [ i ] − 2 ] sa[rk[i]-2] sa[rk[i]−2] 的最长公共前缀长度 − 1 -1 −1
证明:
先分类讨论
当
h
e
i
g
h
t
[
r
k
[
i
−
1
]
]
≤
1
height[rk[i-1]]\leq1
height[rk[i−1]]≤1时,
上面的式子显然成立,因为右边小于等于
0
0
0,而左侧一定大于等于
0
0
0。
当
h
e
i
g
h
t
[
r
k
[
i
−
1
]
>
1
height[rk[i-1]>1
height[rk[i−1]>1 时,
设后缀
i
−
1
i-1
i−1 为
a
A
D
aAD
aAD
(
(
(A是一个长度为
h
e
i
g
h
t
[
r
k
[
i
−
1
]
]
−
1
height[rk[i-1]]-1
height[rk[i−1]]−1的字符串
)
)
),那么后缀
i
i
i就是
A
D
AD
AD。设后缀
s
a
[
r
k
[
i
−
1
]
−
1
]
sa[rk[i-1]-1]
sa[rk[i−1]−1]为
a
A
B
aAB
aAB,那么
l
c
p
(
i
−
1
,
s
a
[
r
k
[
i
−
1
]
−
1
]
)
=
a
A
lcp(i-1,sa[rk[i-1]-1])=aA
lcp(i−1,sa[rk[i−1]−1])=aA。那么由于后缀
s
a
[
r
k
[
i
−
1
]
−
1
]
+
1
sa[rk[i-1]-1]+1
sa[rk[i−1]−1]+1是
A
B
AB
AB,一定排在后缀
i
i
i前面,那么后缀
s
a
[
r
k
[
i
]
−
1
]
sa[rk[i]-1]
sa[rk[i]−1]一定含有后缀
A
A
A,所以
l
c
p
(
i
,
s
a
[
r
k
[
i
]
−
1
]
)
lcp(i,sa[rk[i]-1])
lcp(i,sa[rk[i]−1])至少是
h
e
i
g
h
t
[
r
k
[
i
−
1
]
−
1
height[rk[i-1]-1
height[rk[i−1]−1。
直观地表现一下:
i
−
1
=
a
A
D
i-1=aAD
i−1=aAD
i
=
A
D
i=AD
i=AD
s
a
[
r
k
[
i
−
1
]
−
1
]
=
a
A
B
sa[rk[i-1]-1]=aAB
sa[rk[i−1]−1]=aAB
s
a
[
r
k
[
i
−
1
]
−
1
]
+
1
=
A
B
sa[rk[i-1]-1]+1=AB
sa[rk[i−1]−1]+1=AB
s
a
[
r
k
[
i
]
−
1
]
=
A
[
B
/
C
]
sa[rk[i]-1]=A[B/C]
sa[rk[i]−1]=A[B/C]
l
c
p
(
i
,
s
a
[
r
k
[
i
]
−
1
]
)
=
A
X
(
X
可
能
为
空
)
lcp(i,sa[rk[i]-1])=AX(X可能为空)
lcp(i,sa[rk[i]−1])=AX(X可能为空)
4) O ( n ) 求 h e i g h t 数 组 − C o d e O(n)求height数组-Code O(n)求height数组−Code
for(int i=1,k=0;i<=n;++i)
{
if(k) --k;
while(s[i+k]==s[sa[rk[i]-1]+k]) ++k;
ht[rk[i]]=k;
}
k ≤ n k\le n k≤n,最多减 n n n次,所以最多加 2 n 2n 2n,总复杂度就是 O ( n ) O(n) O(n)。