后缀数组倍增算法代码及解释

经历

最近在学习后缀数组,我发现后缀数组那20多行代码10多个for,真的是太抽象了,基数排序又不是很会所以学起来异常艰难,在冥思苦想以及抱神犇(Dumpling)的大腿后终于有些理解了,将自己写的注释发出来帮助和我一样的人= =.(我觉得我对代码的讲解还是比较细了,但是我没有讲方法,只讲了代码,方法应该比较好懂.)

讲解

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=15000+10;

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]]表示字符串中<=i的字符个数,那么i应该在第 c[x[i]]个(同字符并不影响),因为sa从0开始,所以名次减一,但将这个拿走之后会少一个字符,c[x[i]]要-1,所以直接-- .即这一趟按单个字母排 . */
    for(int k=1;k<n;k<<=1) 
    {   //倍增,将k凑成k*2长度.
        int p=0;
        for(i=n-k;i<n;i++) y[p++]=i;
        /* #define 第一关键字 G1 #define 第二关键字 G2 这是一些长度不够的后缀,G2排序时一定会排在前面(G2全为'\000'(因为有'全'所以是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个位置并与那里的G1合并,那么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=0;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里面名次最大的那个 */

        memset(t2,-1,sizeof(t2));
        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 ;
}

int main()
{
    scanf("%s",s);
    n=strlen(s);
    build('z'+1);
    return 0;
}

你可能感兴趣的:(后缀数组倍增算法代码及解释)