zoukankan      html  css  js  c++  java
  • 用倍增法构造后缀数组中的SA及RANK数组

    感觉后缀数组很难学的说= = 不过总算是啃下来了

    首先 我们需要理解一下倍增法构造的原理

    设原串的长度为n 对于每个子串 我们将它用''补成长度为2^k的串(2^k-1<n<=2^k)

    比如串aba的子串就有 aba''    ba''''  a''''''

    每次操作我们可以排出所有长度为 2^x的子串的大小

    比如串aba的排序过程

    第一遍 a                   a             b

    第二遍 a''             ab           ba

    第三遍 a''''''   aba''    ba''''

    理解这些后 我们可以先写一个 nlog^2n的快排实现的方法

    这种方法比较好写 如果n<=10^5就放心地去用吧

    //SA nlog^2n
    #include <cstdio>
    #include <cstring>
    #include <cmath>
    #include <algorithm>
    #define rep(i,n) for(int i=1;i<=n;++i)
    #define imax (x>y?x:y)
    #define imax (x<y?x:y)
    using namespace std;
    const int N=100010;
    struct node
    {
        int x,y,ma;
    }tr[N];
    char ch[N];
    int r[N<<1],sa[N];
    int n;
    bool cmp(node aa,node bb)
    {
        return aa.x<bb.x||(aa.x==bb.x&&aa.y<bb.y);
    }
    void getsa()
    {
        for(int i=1;1<<(i-1)<n;++i)
        {
            rep(j,n)
            {
                tr[j].x=r[j];
                tr[j].y=r[j+(1<<i-1)];
                tr[j].ma=j;
            }
            sort(tr+1,tr+1+n,cmp);
            int cnt=0;
            rep(j,n)
            r[tr[j].ma]=tr[j].x==tr[j-1].x&&tr[j].y==tr[j-1].y?cnt:++cnt;
        }
        rep(j,n)
        sa[r[j]]=j;
    }
    int main()
    {
        scanf("%s",ch+1);
        n=strlen(ch+1);
        rep(i,n)
        r[i]=ch[i];
        getsa();
        printf("RANK: ");
        rep(i,n)
        printf("%d ",r[i]);
        printf("
    SA:   ");
        rep(i,n)
        printf("%d ",sa[i]);
        return 0;
    }

    然而 考虑到rank数组的特殊性(一定<=n) 我们还可以使用基数排序把复杂度降到nlogn

    这样就可以解决n<=10^6的问题啦

    然而这个的确比较容易写错 并且需要先掌握基数排序的原理

    基数排序从直观上是需要链表去做的 然而只用一个数组也同样可以很方便的实现

    具体可以参考下代码

    //SA nlogn(n=1需要特判下 这里懒得写了)
    #include <cstdio>
    #include <cstring>
    #include <cmath>
    #include <algorithm>
    #define rep(i,n) for(int i=1;i<=n;++i)
    #define imax (x>y?x:y)
    #define imax (x<y?x:y)
    using namespace std;
    const int N=1000010,S=128;//通常字符都是在0-127之间的
    char ch[N];
    int sum[N],r[2][N<<1],sa[2][N];
    int n,t;
    void getsa(int i)
    //这里面的SA值并非最后的SA值 但保证对应rank值相等的一定相邻 从而方便比较大小
    {
        memset(sum,0,sizeof(sum));
        rep(j,n)
        ++sum[r[t][j+i]];
        rep(j,n)
        sum[j]+=sum[j-1];
        rep(j,n)
        sa[0][sum[r[t][j+i]]--]=j;
        memset(sum,0,sizeof(sum));
        rep(j,n)
        ++sum[r[t][j]];
        rep(j,n)
        sum[j]+=sum[j-1];
        for(int j=n;j;--j)
        //基数排序从第二次排序开始是一定要倒序找的(如果不懂的话自行搜索下基数排序)
        sa[1][sum[r[t][sa[0][j]]]--]=sa[0][j];
    }
    int main()
    {
        scanf("%s",ch+1);
        n=strlen(ch+1);
        rep(i,n)
        sum[ch[i]]=1;
        for(int i=1;i<S;++i)
        sum[i]+=sum[i-1];
        rep(i,n)
        r[0][i]=sum[ch[i]];//函数外的sum用于求出初始排名
        for(int i=1;i<n;i<<=1)
        {
            getsa(i);
            t^=1;
            rep(j,n)
            r[t][sa[1][j]]=r[t^1][sa[1][j]]==r[t^1][sa[1][j-1]]&&
                        r[t^1][sa[1][j]+i]==r[t^1][sa[1][j-1]+i]?
                        r[t][sa[1][j-1]]:r[t][sa[1][j-1]]+1;
            if(r[t][sa[1][n]]==n)break;//已经排好序了便可以提前退出
        }
        printf("RANK: ");
        rep(i,n)
        printf("%d ",r[t][i]);
        printf("
    SA:   ");
        rep(i,n)
        printf("%d ",sa[1][i]);
        return 0;
    }
  • 相关阅读:
    AOP之PostSharp3MethodInterceptionAspect
    AOP之PostSharp6EventInterceptionAspect(事件异步调用)
    C# Winform获取路径
    C#生成唯一的字符串或者数字
    【电信增值业务学习笔记】1 初步学习
    【读书笔记】《产品经理手册》
    【协议学习】PPPoE学习文档
    【电信增值业务学习笔记】2 移动网络基本概念和组网结构
    【电信增值业务学习笔记】3 语音类增值业务
    【通信基础知识】白噪声、相关解调和相干解调
  • 原文地址:https://www.cnblogs.com/sagitta/p/4638491.html
Copyright © 2011-2022 走看看