0)后缀数组连续看了几个晚上才弄明白。思想很简单,但是实现的细节和技巧却需要好好推敲。
1)后缀数组,倍增算法(介绍了 后缀数组sa,名次数组rank_,以及heigh数组)
大体介绍一下仅在下面代码中的这三个数组的一些特点,对于理解代码很有帮助。首先约定在此处介绍中,len是字符串实际长度(不补0的情况下):sa[],下标是该后缀的名次(1到len),值是该后缀的首字符在字符串中的位置(0到len-1);rank_[],下标是该后缀首字符在字符串中的位置(0到len-1),值是该后缀的名次(1到len);heigh[],下标是该后缀的名次(1到len),值是sa[i]与sa[i-1]的公共前缀长度,且heigh[1]是第一名次的后缀与第零名次后缀的公共前缀长度,因为第零名次后缀不存在所以heigh[1]也没有意义。
①未注释版
#include <iostream>
#include <string.h>
#include <stdio.h>
using namespace std;
const int maxn=100010;
string fir,sec;
int m=27;
int c[27];
int sa[maxn],rank_[maxn],heigh[maxn],t2[maxn],t1[maxn];
void GetSa(int *r){
int *x=t1,*y=t2;
int i;
int n=fir.size();
n++;
for(i=0;i<m;i++) c[i]=0;
for(i=0;i<n;i++) c[x[i]=r[i]]++;
for(i=1;i<m;i++) c[i]+=c[i-1];
for(i=n-1;i>=0;i--) sa[--c[x[i]]]=i;
for(int k=1;k<n;k<<=1){
int p=0;
for(i=n-k;i<n;i++) y[p++]=i;
for(i=0;i<n;i++) if(sa[i]>=k) y[p++]=sa[i]-k;
for(i=0;i<m;i++) c[i]=0;
for(i=0;i<n;i++) c[x[y[i]]]++;
for(i=1;i<m;i++) c[i]+=c[i-1];
for(i=n-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i];
swap(x,y);
p=1;x[sa[0]]=0;
for(i=1;i<n;i++)
x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?p-1:p++;
if(p>=n){
for(int i=1;i<n;i++) rank_[sa[i]]=i;
break;
}
m=p;
}
}
int Cmp_suffix(string pattern,int p,int n2){
const char *temp=pattern.c_str();
const char *temp2=fir.c_str();
return strncmp(temp,temp2+sa[p],n2);
}
int Find(string pattern){
int n2=pattern.size();
int n=fir.size();
if(Cmp_suffix(pattern,1,n2)<0) {cout<<"1处退出"<<endl;return -1;}
if(Cmp_suffix(pattern,n,n2)>0) {cout<<"2处退出"<<endl;return -1;}
int L=1,R=n;
while(L<=R){
int M=(L+R)/2;
int res=Cmp_suffix(pattern,M,n2);
if(res==0) return M;
if(res<0) R=M-1;
else if(res>0) L=M+1;
}
cout<<"3处退出"<<endl;
return -1;
}
void GetHigh(int *r){
int k=0,i,j;
int n=fir.size();
for(i=0;i<n;i++){n;
j=sa[rank_[i]-1];
if(k) k--;
while(r[i+k]==r[j+k]) k++;
heigh[rank_[i]]=k;
}
}
int main()
{
cin>>fir;
int n=fir.size();
int r[maxn];
for(int i=0;i<n;i++) r[i]=fir[i]-'a'+1;
r[n]=0;
GetSa(r);
///输出后缀:
for(int i=1;i<=n;i++){
const char *temp=fir.c_str();
cout<<temp+sa[i]<<endl;
}cout<<endl;
///输出后缀数组:
for(int i=1;i<=n;i++) cout<<sa[i]<<endl; cout<<endl;
///输出名次数组
for(int i=0;i<n;i++) cout<<rank_[i]<<endl; cout<<endl;
GetHigh(r);
///输出heigh数组
for(int i=1;i<=n;i++) cout<<heigh[i]<<endl; cout<<endl;
///输入模板字符串pattern,二分查找在fir字符串中是否存在前缀是pattren 的后缀。(即二分查找并判断该模板字符串pattern是否是fir字符串或者其子串的前缀)
string pattern;
cin>>pattern;
int res=Find(pattern);
if(res==-1)
cout<<"Not Found.";
else{
int p=res;
for(int i=sa[p];i<n;i++){
cout<<fir[i];
}
cout<<endl;
}
return 0;
}
②注释版
#include <iostream>
#include <string.h>
#include <stdio.h>
using namespace std;
const int maxn=100010;//视情况而定,不用因为y[sa[i]+k]而使y的长度比其他数组如sa、t1等长度更大从而maxn要么不统一适用要么就额外增大, 因为r[n]=0,所以y[sa[i]]==y[sa[i-1]]作为第一限制条件使得&&后面的y[sa[i]+k]==y[sa[i-1]+k]不会越界。
string fir,sec;
int m=27;
int sa[maxn];//后缀数组
int rank_[maxn];//名次数组,但在GetSa中并非真实的rank值,只不过能用它比较各个字符串从而起到rank的作用(如qwert的5个后缀的rank值本应该是2 5 1 3 4,但是在GetSa之后未加任何处理之前得到的rank_数组是,17 23 5 18 20)
int heigh[maxn];//sa[i]与sa[i-1]的最长公共前缀的长度
int c[27];//统计各个字符,出现的次数,我们假设现在的字符串中完全由字母组成,而在代码中带入的下标是,某字符-'a'+1,所以用到的下标是从1到26,长度大于等于26即可
int t2[maxn];
int t1[maxn];
void GetSa(int *r){
int *x=t1,*y=t2;//
int i;
int n=fir.size();
n++;//因为r[n]==0,所以字符的个数加1
//对于所有一个字符长度的后缀进行基数排序,并得到第一次的sa[]数组
for(i=0;i<m;i++) c[i]=0;
for(i=0;i<n;i++) c[x[i]=r[i]]++;//x[]先存储原字符串 的Asc码,虽然此时不是rank值,但是起到了rank值的作用,即能在单个字符间互相比较大小,从而容易得到顺序
for(i=1;i<m;i++) c[i]+=c[i-1];//累计是第几个
for(i=n-1;i>=0;i--) sa[--c[x[i]]]=i;
for(int k=1;k<n;k<<=1){
int p=0;
//利用基数排序求得的sa数组,根据第二关键值(当前值是合并后的第二关键值所以往前移k个假如不越界的话,或者当前值的位置处在太靠后的位置以至于处在该位的合并后的数的第二关键值因为没有所以默认为0),进行排序
for(i=n-k;i<n;i++) y[p++]=i; //先将没有第二关键值(即默认为0)的数放进y数组(根据第二关键值排序),且所以他们的下标自然是当前位置
for(i=0;i<n;i++) if(sa[i]>=k) y[p++]=sa[i]-k;//因为是将当前位置的数当做合并后的数的第二关键值来看,所以他们合并后的位置,自然是-k,自然要先if判断合并后的位置是否越界。
//根据合并后的值得第一关键值,进行基数排序
for(i=0;i<m;i++) c[i]=0;
for(i=0;i<n;i++) c[x[y[i]]]++;//y[i]是合并后的位置,也就是合并前第一个数的位置,所以x[y[i]]就是这个位置上的数的asc码
for(i=1;i<m;i++) c[i]+=c[i-1];
for(i=n-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i];
//
swap(x,y);
p=1;x[sa[0]]=0;
for(i=1;i<n;i++)
x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?p-1:p++;//可能会有名次相同的二元组
if(p>=n){
for(int i=1;i<n;i++) rank_[sa[i]]=i;//得到真正的rank值,因为之前r[n]=0,导致sa[0]=n,rank_[n]=0。即sa真正的对于字符串的下标从1开始而sa的值(对应字符串中的位置)从0开始;rank_的下标(对应字符串中的位置)从0开始,而rank_值从1开始。即名次都是从1开始,位置都是从0开始。
break;
}//每一个二元组的名次在此时都是唯一的,所以不需再继续倍增比较了
m=p;
}
}
int Cmp_suffix(string pattern,int p,int n2){//p 是 后缀的名次,sa[p]是第p名后缀的首字符的位置,那么fir+sa[p]是第p名的后缀字符串
const char *temp=pattern.c_str();
const char *temp2=fir.c_str();
return strncmp(temp,temp2+sa[p],n2);//n2是比较的长度,strncmp(str1,str2,len)比较函数,比较len长度,若str1的ASC码等于str2则返回0,否则返回str1的ASC-str2的ASC
}
int Find(string pattern){
int n2=pattern.size();//n2是模板的长度
int n=fir.size();
if(Cmp_suffix(pattern,1,n2)<0) {cout<<"1处退出"<<endl;return -1;}
if(Cmp_suffix(pattern,n,n2)>0) {cout<<"2处退出"<<endl;return -1;}
int L=1,R=n;
while(L<=R){
int M=(L+R)/2;
int res=Cmp_suffix(pattern,M,n2);
if(res==0) return M;//模板sec是第M名后缀的前缀
if(res<0) R=M-1;
else if(res>0) L=M+1;
}
cout<<"3处退出"<<endl;
return -1;
}
void GetHigh(int *r){
int k=0,i,j;
int n=fir.size();
for(i=0;i<n;i++){//这里的n就是字符串的实际长度没有加上最后一个自己加的0,所以<=n //另外rank的下标虽然也是描述位置,但是是从1开始的。因为赋值时的rank[sa[i]]=i;而sa[0]=n;sa[1]=真正的字符串中第一个(ASC最小)后缀的首字符所在位置
j=sa[rank_[i]-1];
if(k) k--;
while(r[i+k]==r[j+k]) k++;
heigh[rank_[i]]=k;//极端情况是,rank_[i]==1,那么此时那么j==sa[0]==n,j+k>=n+0,r[n]是自己加的0,因为字母中ASC最小的'a'对应的r数组中的值也是1,所以r[i+k]!=r[n]。只需注意,因为heigh[i]定义是sa[i]与sa[i-1]的公共前缀的长度,所以heigh[0]是没有意义的。
}
}
int main()
{
cin>>fir;
int n=fir.size();
int r[maxn];
for(int i=0;i<n;i++){
r[i]=fir[i]-'a'+1;//从1开始,就可以检验"aaaaa"的字符串了,否则因为a-'a',得到0,排序aaa...aaa会有错
}
r[n]=0;//防止越界,用于GetSa中最后一个for循环中的检验数据,相同字母的字符串
GetSa(r);
///输出后缀:
for(int i=1;i<=n;i++){
const char *temp=fir.c_str();
cout<<temp+sa[i]<<endl;
}cout<<endl;
///输出后缀数组:
for(int i=1;i<=n;i++) cout<<sa[i]<<endl; cout<<endl;
///输出名次数组
for(int i=0;i<n;i++) cout<<rank_[i]<<endl; cout<<endl;
GetHigh(r);
///输出heigh数组
for(int i=1;i<=n;i++) cout<<heigh[i]<<endl; cout<<endl;
///输入模板字符串pattern,二分查找在fir字符串中是否存在前缀是pattren的后缀。(即二分查找并判断该模板字符串pattern是否是fir字符串或者其子串的前缀)
string pattern;//等待比较的模板,寻找该模板是不是字符串fir的某一个后缀的前缀
cin>>pattern;
int res=Find(pattern);
if(res==-1)
cout<<"Not Found.";
else{
int p=res;
for(int i=sa[p];i<n;i++){
cout<<fir[i];
}
cout<<endl;
}
return 0;
}