浅谈后缀数组SA

不打扰是莪最后的温柔 提交于 2019-12-03 08:28:23

这篇博客不打算讲多么详细,网上关于后缀数组的blog比我讲的好多了,这一篇博客我是为自己加深印象写的。

给你们分享了那么多,容我自私一回吧~

参考资料:这位dalao的blog

一、关于求SuffixArray的一些变量定义:

1. sa[i]=j,表示第i名的后缀从j开始

**存的是下标**

2. rnk[i]=j,从i开始的后缀是第j名的

**与sa为互逆运算,存的是值**

3. tp[i]=j, 第二关键字为i的后缀从j开始

**可理解为第二关键字的SA,存的是下标**

插入解释一下第一关键字和第二关键字:

我们要对所有的后缀进行排序,怎么排呢?

开始时,我们每个字符的后缀存的只有它自己,所以它后缀的大小就是它的ASCII码。

我们把每个字符i看成(s[i],i)的二元组,如果我们直接丢pair<int,int>里面然后std::sort,

这样的时间复杂度是O(log^2 n)的,显然不够优秀。

所以就需要用到基数排序RadixSort,不了解的自行百度。

再使用倍增法,就可以使我们排序的时间复杂度降低到O(logn)。

所以我们要对每个后缀的前两个字母进行排序,第一个字母的相对关系已经得到了。

第i个后缀的第二个字母,就是第i+1个后缀的第一个字母,利用这个关系我们第二个字母的相对关系也就知道了。

我们的tp数组就是用来记录它的,rnk[i]表示上一轮中第i个后缀的排名。

这里引用神仙attack的一句话,我觉得讲的非常到位:

对于一个长度为w的后缀,你可以形象的理解为:

第一关键字针对前w2个字符形成的字符串,第二关键字针对后w2个字符形成的字符串

然后对每个后缀的前4个字母组成的字符串排序,前8个,前16个...这就是倍增法求SA的流程了。

给出RadixSort的代码:

void RadixSort(int a[],int b[]){//基数排序 
    for(int i=0;i<=m;i++)tax[i]=0;
    for(int i=1;i<=n;i++)tax[a[i]]++;
    for(int i=1;i<=m;i++)tax[i]+=tax[i-1];
    for(int i=n;i>=1;i--)sa[tax[a[b[i]]]--]=b[i];
}

实在不能理解RadixSort也没有关系,代码很短

再给出求SA的代码:

bool cmp(int *r,int a,int b,int k){
    return r[a]==r[b]&&r[a+k]==r[b+k];
}
void getSA(int a[],int b[]){
    for(int i=1;i<=n;i++)
        m=max(m,a[i]=s[i]-'0'),b[i]=i;
    RadixSort(a,b);
    for(int p=0,j=1;p<n;j<<=1,m=p){
        p=0;
        for(int i=1;i<=j;i++)b[++p]=n-j+i;
        for(int i=1;i<=n;i++)if(sa[i]>j)b[++p]=sa[i]-j;
        RadixSort(a,b);
        int *t=a;a=b;b=t;
        a[sa[1]]=p=1;
        for(int i=2;i<=n;i++)
            a[sa[i]]=cmp(b,sa[i],sa[i-1],j)?p:++p;
    }
}

关于代码的解释,有时间再填坑。本蒟蒻要学的算法还很多...SA就粗略地理解一下好了

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!