后缀数组
emm,被某fatmouse吐槽注册这么久都没发过博客了,那就开始写写吧。
这个就是记一下后缀数组
后缀数组与名次数组都是要字符串内所有字符的后缀按字典序排序,后缀数组记录的是某一序号对应的是哪个后缀,名次数组记录的是该后缀的对应序号
举个例子(其中sa是后缀数组,Rank是名次数组)
这就是将‘aabaaaab’这个字符串中的每个字符的后缀(包括它自己)排序后, Rank存字符串中每一个后缀所对应的序号, sa存每一个序号所对应的后缀。这样不难看出, 只要求出名次数组或后缀数组中的一个, 就很容易求出对应的另一个数组,即当sa[i]=k, 则Rank[k]=i。
既然知道了名次数组与后缀数组的关系,以及求出它们的方法, 那么就可以写出求这两个数组的模板了, 这里引用了复旦大学吴永辉老师的代码及注释
struct node{
int now,nxt;
}d[maxn];
int val[maxn][2], c[maxn], Rank[maxn], sa[maxn], pos[maxn], x[maxn];
int n;
void add_value(int u, int v, int i)
{
d[i].nxt=c[u];
c[u]=i;
d[i].now=v;
}
void radix_sort(int l, int r)
{
for(int k=1; k>=0; k--)
{
memset(c, 0, sizeof(c));
for(int i=r; i>=l; i++)
add_value(val[pos[i]][k], pos[i], i);
int t=0;
for(int i=0; i<=20000; i++)
for(int j=c[i]; j; j=d[j].nxt )
pos[++t]=d[j].now;
}
int t=0;
for(int i=l; i<=n; i++)
{
if(val[pos[i]][0]!=val[pos[i-1]][0]||val[pos[i]][1]!=val[pos[i-1]][1]) t++;
Rank[pos[i]]=t;
}
}
void get_suffix_array()
{
int t=1;
while(t/2<=n){
for(int i=1; i<=n; i++) {
val[i][0]=Rank[i];
val[i][1]=(((i+t/2<=n)?Rank[i+t/2]:0));
pos[i]=i;
}
radix_sort(1, n);
t*=2;
}
for(int i=1; i<=n; i++) sa[Rank[i]]=i;
}
这个中运用到了倍增算法
倍增算法是对每个字符开始的长度为
2
k
2^k
2k(
k
>
=
0
k>=0
k>=0)的子串进行排序, k每次*2, 且每一次的排序都是基于上一次排序得出的Rank,设开始地址为i的长
2
k
2^k
2k的子串的Rank的关键字为xy, x既是开始地址i,, 长
2
k
−
1
2^{k-1}
2k−1的字符串的排序 ,y为i+
2
k
−
1
2^{k-1}
2k−1,长
2
k
−
1
2^{k-1}
2k−1的字符串的排序 ,(若y超出了字符串, 则用0补上), 对每个长
2
k
2^k
2k的字符串排名关键字xy进行基数排序, 则可得到其Rank值。当
2
k
2^k
2k>字符串长度时, 得到的Rank值就是最后结果
举例:
了解了这些后, 再来看一下后缀数组一个应用:求最长公共前缀
首先我们设heigtht[i]为suffix(SA[i-1])和suffix(SA[i])的最长公共前缀的长度,对于j和k,如果Rank[j]<Rank[k],则有以下性质:
suffix(j)和suffix(k)的最长公共前缀的长度是height[Rank[j]]到height[Rank[k]]的最小值。
根据这张表可以更直观的看出, 'abaaaab’和’aaab’的最长公共前缀就是从height[Rank[5]]到height[Rank[2]]的最小值1
那么这边也是引用一下吴永辉老师的最长公共前缀的模板
int h[maxn], height[maxn];
void get_common_prefix()
{
memset(h, 0 ,sizeof(h));
for(int i=1; i<=n; i++)
{
if(Rank[i]==1)
h[i]=0;
else {
int now=0;
if(i>1&&h[i-1]>1) now=h[i-1]-1;
while(now+i<=n&&now+sa[Rank[i]-1]<=n&&x[now+i]==x[now+sa[Rank[i]-1]])
now++;
h[i]=now;
}
}
for(int i=1; i<=n; i++)
height[Rank[i]]=h[i];
}
后缀数组还有很多用处, 他被广泛用于字符串处理, 其优点在于
1。避免“蛮力”搜索, 便于优化算法
2。时空效率高, 程序有标准模板
emm,就到这儿吧, 溜了溜了