首页 > 学院 > 开发设计 > 正文

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

2019-11-06 09:12:25
字体:
来源:转载
供稿:网友

什么是后缀:一个字符串从某位开始到其末尾的子串。 什么是后缀数组:一个字符的所有后缀从小到大排列所形成的的数组。 后缀数组可以解决很多的字符串问题,本文主要写的是构造后缀数组的倍增算法。 暴力构造:直接把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;}


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表