经历
最近在学习后缀数组,我发现后缀数组那20多行代码10多个for,真的是太抽象了,基数排序又不是很会所以学起来异常艰难,在冥思苦想以及抱神犇(Dumpling)的大腿后终于有些理解了,将自己写的注释发出来帮助和我一样的人= =.(我觉得我对代码的讲解还是比较细了,但是我没有讲方法,只讲了代码,方法应该比较好懂.)
讲解
#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=15000+10;
const char msg[]="ABCDEFG";
char s[maxn];
int sa[maxn],t[maxn],t2[maxn],c[maxn],n;
void build(int m)
{
int i,*x=t,*y=t2;
for(i=0;i<m;i++) c[i]=0;
for(i=0;i<n;i++) c[x[i]=s[i]]++;
for(i=1;i<m;i++) c[i] += c[i-1];
for(i=n-1;i>=0;i--)
sa[--c[x[i]]]=i;
/*
解读:c[x[i]]表示字符串中<=x[i]的字符个数,那么i应该在第
c[x[i]]个(同字符并不影响),因为sa从0开始,所以名次减
一,但将这个拿走之后会少一个字符,c[x[i]]要-1,所以直接
-- .即这一趟按单个字母排 .
*/
for(int k=1;k<n;k<<=1)
{ //倍增,将k凑成k*2长度,k>=n就没必要了
int p=0;
for(i=n-1;i>=n-k;i--) y[p++]=i;
/*
#define G1 第一关键字
#define G2 第二关键字
这是一些长度不够的后缀,G2排序时一定会排
在前面(G2全为'\0'(因为有'全'所以是n-k
而不是n-k*2+1,倍增出来的G2是经过合并的))
这里将n-k到n-1放进了y数组
*/
for(i=0;i<n;i++)
if(sa[i]>=k) y[p++]=sa[i]-k;
/*
合并过程中需要将G2往前移k个位置,那么y
(现在是按G2在排)中,y中G2的相对排名和原来的G2是一
样的,所以直接复制过来,(也可以理解为sa并没
有提供n-k~n-1这一段的有效信息,因为G2是空的)
这里将0~n-k-1放进了y数组
综上,这两行将0~n-1不重不漏的放进了y数组.
*/
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];
/*
这4行对G2已经排好序的y进行G1排序,仍然是基数
排序,不过因为G2变了(排单个字母时也可理解为
G2全为空),不能再用i而要用y[i],想一想如果G2
全空的话无论什么顺序都可以.
之所以要倒着加是保证按照G2已经排好的顺序,
因为c[x[y[i]]]是相同G1里面名次最大的那个
QAQ我终于懂了
*/
swap(x,y);
p=1;x[sa[0]]=0;//sa[0]是编号最小的那个后缀
for(i=1;i<n;i++)
if(y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k])
//G1==G'1&&G2==G'2
x[sa[i]]=p-1;
else x[sa[i]]=p++;
if(p>=n) break;
m=p;
}
return ;
}
void debug(){
for(int i=0;i<n;i++)
{
printf("%d ",i);
for(int j=sa[i];j<n;j++)
{
printf("%c",s[j]);
}
printf("\n");
}
return ;
}
int main()
{
scanf("%s",s);
n=strlen(s);
build('}'+1);
debug();
return 0;
}