用倍增法构造后缀数组中的SA及RANK数组

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

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

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

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

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

比如串aba的排序过程

第一遍 a                   a             b

第二遍 a'\0'             ab           ba

第三遍 a'\0''\0''\0'   aba'\0'    ba'\0''\0'

理解这些后 我们可以先写一个 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("\nSA:   ");
    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("\nSA:   ");
    rep(i,n)
    printf("%d ",sa[1][i]);
    return 0;
}
posted @ 2015-07-11 13:34  sagitta  阅读(980)  评论(0编辑  收藏  举报