后缀数组

  西安回来后一个月没写代码了,前段时间太忙,这两天算是好点了,学习了一下后缀数组。

  这里有几个关键的数组。

  s是原始字符数组,sa是后缀数组,sa[i]表示第i大后缀的开始位置,rank是名次数组,rank[i]表示位置i的数组是第几大(相当于sa[i]反过来),height[i]是后缀sa[i]和后缀sa[i-1]的最长公共前缀,x是当前rank,y是辅助数组。

  主要思路就是这个图,每次给后缀的前2^k个字符排序,最多logn次排序,如果用基数排序复杂度为O(n),总复杂度为O(nlogn)。

  后缀数组_第1张图片


  关于height数组的计算:设h[i]=height[rank[i]],h[i]>=h[i-1]-1(因为后缀i比后缀i-1少最左边一个字符,因此h[i]最小都是h[i-1]-1),这样不必每次从头比较了,复杂度O(n)。

  有一点要注意的是,一般在s数组最后加一个0,并且其它元素都非0。这是为了防止数组越界,省去了特判的麻烦(因为如果s为1111,sa={0,1,2,3},k=1,计算x的时候比较最后两个元素第一维的时候相等,此时比较第二维,最后一个位置+1就越界了,因为最后一个位置是没有第二维的),加了个0后,如果第一维相等,也就是y[sa[i-1]]==y[sa[i]],这个时候sa[i-1]+k和sa[i]+k是小于n的,因为如果第一维相等,两个串最后一个字符肯定不会是0,否则就不会相等(因为只有最后一个字符是0),所以加了k也不会越界。

struct SuffixArray{
    int s[MAXN];            //原始字符数组
    int sa[MAXN];           //后缀数组,sa[i]为第i小后缀在s中的下标,最后一个字符是0,前面非0
    int rank[MAXN];         //名次数组,rank[i]为s[i]后缀是第几小,rank[n-1]=0
    int height[MAXN];       //height[i]为sa[i-1]和sa[i]的最长公共前缀
    int c[MAXN];            //基数排序数组
    int t[MAXN],t2[MAXN];   //x,y辅助数组
    int n;                  //字符个数

    void clear(){
        n=0;
        memset(sa,0,sizeof(sa));
    }
    //m为最大字符值+1,调用前需设置好s和n
    void build_sa(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;
        for(int k=1;k<=n;k<<=1){
            int p=0;
            //用sa数组排序第二关键字
            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(int i=0;i<m;i++) c[i]=0;
            for(int i=0;i<n;i++) c[x[y[i]]]++;
            for(int i=1;i<m;i++) c[i]+=c[i-1];
            for(int i=n-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i];
            swap(x,y);
            p=1;
            x[sa[0]]=0;
            for(int i=1;i<n;i++) x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++;
            if(p>=n) break;
            m=p;
        }
    }
    void build_height(){
        int k=0;
        for(int i=0;i<n;i++) rank[sa[i]]=i;
        height[0]=0;
        for(int i=0;i<n-1;i++){
            if(k) k--;
            int j=sa[rank[i]-1];
            while(s[i+k]==s[j+k]) k++;
            height[rank[i]]=k;
        }
    }
};


你可能感兴趣的:(后缀数组)