后缀数组[学习笔记]

后缀数组[学习笔记]

这几天打算系统地学习一下字符串有关的东西...

今天看了看国家集训队的论文,做了几道板子题,感觉还是很有收获的,也算稍微把后缀数组的坑填了一下吧。

倍增算法求SA数组

利用倍增的思想来求后缀数组。

方法大概就是用基数排序,第一次按照每个后缀开头的字符为关键字,排一遍序,得到第一遍的rank。然后扩展到以开头两个字符再排序,这两个字符分别用上一次他们的rank,两个关键字进行基数排序。然后长度再乘2,扩展到4个...就这样一直扩展直到没有两个后缀排名相等就好了

代码以及注释

int n,h[maxn],sa[maxn],rk[maxn],tax[maxn],tp[maxn],m;
char s[maxn];
void Qsort(){
    for(int i=0;i<=m;i++) tax[i]=0;
    for(int i=1;i<=n;i++) tax[rk[i]]++;//按照第一关键字划分得到每个桶的大小
    for(int i=1;i<=m;i++) tax[i]+=tax[i-1];//前缀和来得到每一组最后一位的排名
    for(int i=n;i>=1;i--)
        sa[tax[rk[tp[i]]]--]=tp[i];//第二关键字的桶尾--,填入元素
}//基数排序,其中tp为第二关键字,rk为第一关键字
void getsa(){
    m=126;//m为字符集大小
    for(int i=1;i<=n;i++)
        rk[i]=s[i]-'a',tp[i]=i;//得出第一次的rk和tp
    Qsort();//第一次排序
    for(int p=1,w=1;p<n;m=p,w<<=1){
        p=0;
        for(int i=1;i<=w;i++) tp[++p]=n+i-w;//对于最后w个,他们的第二关键字所在的位置已经
        for(int i=1;i<=n;i++)       //超过字符串长,第二关键字为空,字典序肯定是最小的
            if(sa[i]>w) tp[++p]=sa[i]-w;   //将自己的rk赋给位置减w的第二关键字
        Qsort();
        swap(rk,tp);//tp用不到了,用它来装上一次的rk
        rk[sa[1]]=p=1;
        for(int i=2;i<=n;i++)
            rk[sa[i]]=tp[sa[i]]==tp[sa[i-1]]&&tp[sa[i]+w]==tp[sa[i-1]+w]?p:++p;
//上面是按照排名来划分rk,如果他和前一个的第一关键字第二关键字都相等则排名相等,否则rk++
    }
}

求height数组

\(height\)数组的定义:\(h[i]\)表示\(rk\)\(i\)的后缀与\(rk\)\(i-1\)的后缀的最长公共前缀,可以用\(sa\)数组和\(rk\)数组来\(O(n)\)求得,这要用到一个性质:\(h[rk[i]]>=h[rk[i-1]]-1\)。也就是说如果根据\(rk\)从前往后求,每次暴力匹配来增加长度,结果是近似递增的,可以证明时间复杂度的正确性。性质的证明详见论文

代码以及注释

void geth(){
		for(int i=1,j,k=0;i<=n;h[rk[i++]]=k)//i是要求的是第rk位,k为h数组的值
		for(k?k--:0,j=sa[rk[i]-1];s[i+k]==s[j+k];k++);
    	//h[i]>=h[i-1]-1,k要--. 暴力匹配.
}
posted @ 2018-11-28 21:44  nianheng  阅读(182)  评论(0编辑  收藏  举报