后缀数组之倍增算法——学习笔记

什么是后缀:一个字符串从某位开始到其末尾的子串。
什么是后缀数组:一个字符的所有后缀从小到大排列所形成的的数组。
后缀数组可以解决很多的字符串问题,本文主要写的是构造后缀数组的倍增算法。
暴力构造:直接把n个后缀排序。因为比较字符串需要 O(n) 的时间,所以总复杂度 O(n2log2n)
这样完全没有利用到n个元素都是某一字符串的后缀这个特性,如何改进呢?
倍增算法的思想就是比较所有后缀的前缀,并倍增增加当前考察的前缀长度。
具体来说:
假设我们已经对所有后缀只取前k位并排好序,现在我们要把长度增加到k*2。可以发现对于一个后缀i (s[i~n]),他的前k*2位可以分成两部分:
后缀i的前k位 与 后缀i+k的前k位。
考虑比较两个长度同为k*2的字符串:若前k位大小不同就能直接出结果,否则就比较后k位的大小。
这些前缀的排名在之前已经得到了,我们把后缀i的前k位作为第一关键字,后缀i+k的前k位作为第二关键字,这样是不是就可以比较大小了呢?
结合下面这张图会好理解一些:(盗来的)
这里写图片描述
因为关键字其实就是上一次的排名,范围只有1~N,所以我们可以用基数排序的方法O(n)得出当前答案。这样总复杂度为 O(nlog2n) ,很优秀。
下面是我的模板,相比dalao们的繁琐一点,常数大一些,但是可读性还是很强的,不容易写错。

#include<cstdio>
#include<vector>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=500005;
int n,rk[maxn],sa[maxn],tem[maxn],res[maxn];
vector <int> tab[maxn];
char s[maxn];
void csort(int k){
    int m=max(n,400); for(int i=0;i<=m;i++) tab[i].clear();
    for(int i=1;i<=n;i++) tab[rk[tem[i]+k]].push_back(tem[i]);
    tem[0]=0; for(int i=0;i<=m;i++){ int len=tab[i].size(); for(int j=0;j<=len-1;j++) tem[++tem[0]]=tab[i][j]; }
}
void get_SA(){
    for(int i=1;i<=n;i++) rk[i]=s[i]; 
    for(int k=1;k<n;k<<=1){  
        for(int i=1;i<=n;i++) tem[i]=i;
        csort(k); csort(0);  
        int now=0; tem[0]=0;
        for(int i=1;i<=n;i++){
            if(!(rk[tem[i]]==rk[tem[i-1]]&&rk[tem[i]+k]==rk[tem[i-1]+k])) now++;
            res[tem[i]]=now;
        }
        for(int i=1;i<=n;i++) rk[i]=res[i];
    }
    for(int i=1;i<=n;i++) sa[rk[i]]=i;
    //for(int i=1;i<=n;i++) printf("%s\n",s+sa[i]); printf("\n");
}
int main(){
    freopen("sa.in","r",stdin);
    freopen("sa.out","w",stdout);
    scanf("%s",s+1); n=strlen(s+1);
    get_SA();
    return 0;
} 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值