后缀数组
-
后缀,就是从字符串中的一个字符开始直到结束的字串;而后缀数组则能求出字符串中所有后缀的排名。
-
介绍即将登场的数组们:sa[i]记录的是排名为i的后缀是从第几个字符开始的;Rank[i]记录的是从第i个字符开始的后缀排名第几;c[]用于基数排序;
3.代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int N=1000005;
int n,na,Rank[N],sa[N],tmp[N],c[N];
char s[N];
void make_sa()
{
int j,len;
na=max(n,256);
memset(c,0,sizeof(c));
for (int i=0; i<n; i++) c[Rank[i]=s[i]&0xff]++;
for (int i=0; i<na; i++) c[i]+=c[i-1];
for (int i=0; i<n; i++) sa[--c[Rank[i]]]=i;//初始化Rank,c,sa
for (len=1; len<n; len<<=1)
{
for (int i=0; i<n; i++)//i为排名
{
j=sa[i]-len;
if (j<0) j+=n;
tmp[c[Rank[j]]++]=j;//tmp相当于sa
}
sa[tmp[c[0]=0]]=j=0;//sa暂时相当于Rank
for (int i=1; i<n; i++)//i为上一轮排名
{
if (Rank[tmp[i]]!=Rank[tmp[i-1]]||Rank[tmp[i]+len]!=Rank[tmp[i-1]+len]) c[++j]=i;
sa[tmp[i]]=j;//j为当前排名
}
memcpy(Rank,sa,sizeof(Rank)); memcpy(sa,tmp,sizeof(sa));
if (j>=n-1) break;//优化,排名无重复则已完成排序
}
}
int main()
{
scanf("%s",s); s[strlen(s)]='$';
n=strlen(s);
make_sa();
for (int i=1; i<n; i++) printf("%d ",sa[i]+1); printf("
");
return 0;
}
LCP
后缀的排名已经完成,那么有什么用呢?LCP(最长公共前缀)就出现了。
1.H[i]数组表示排名为i的后缀与其他后缀的最长公共前缀的长度。
2.想到既然所有后缀都已排名,那么相邻的后缀相似度最高,可以得到第i个后缀与其他后缀的最长公共前缀即为其与第i-1个的最长公共前缀。
void lcp()
{
int j,k=0;
for (int i=0; i<n; i++) Rank[sa[i]]=i;//初始化
for (int i=0; i<n; i++)//枚举后缀的开始位置
{
if (k) k--;
j=sa[Rank[i]-1];//排名为第i-1个的后缀的开始位置
while (s[i+k]==s[j+k]) k++;//枚举最长长度
H[Rank[i]]=k;
}
}