这里会显示出您选择的修订版和当前版本之间的差别。
两侧同时换到之前的修订记录 前一修订版 后一修订版 | 前一修订版 | ||
2020-2021:teams:hotpot:后缀数组 [2020/09/03 17:04] lotk |
2020-2021:teams:hotpot:后缀数组 [2020/09/03 19:55] (当前版本) lotk |
||
---|---|---|---|
行 26: | 行 26: | ||
{{:2020-2021:teams:hotpot:后缀2.png?600|}} | {{:2020-2021:teams:hotpot:后缀2.png?600|}} | ||
+ | |||
+ | 重复以上过程,我们可以求出长度为 $2^2$ 的排序结果: | ||
+ | |||
+ | {{:2020-2021:teams:hotpot:后缀3.png?600|}} | ||
+ | |||
+ | 不难看出,这个时候,我们已经完成了排序,而最坏的情况下,这种算法也可以在 $logn$ 次完成排序。 | ||
+ | |||
+ | 用快排进行的话,时间复杂度为 $O(nlognlogn)$ | ||
+ | |||
+ | 考虑到所有排序数的范围在 $[-1,n)$ 之间,采取基数排序,能够将复杂度优化到 $nlogn$ | ||
+ | |||
+ | =====模板===== | ||
+ | |||
+ | 下面我们给出一道模板题[[https://www.luogu.com.cn/problem/P3809|P3809 【模板】后缀排序]] | ||
+ | |||
+ | <code cpp> | ||
+ | |||
+ | #include<algorithm> | ||
+ | #include<stack> | ||
+ | #include<ctime> | ||
+ | #include<cstring> | ||
+ | #include<cmath> | ||
+ | #include<iostream> | ||
+ | #include<iomanip> | ||
+ | #include<cstdio> | ||
+ | #include<queue> | ||
+ | using namespace std; | ||
+ | inline int read(){ | ||
+ | int num=0,f=1;char x=getchar(); | ||
+ | while(x<'0'||x>'9'){if(x=='-')f=-1;x=getchar();} | ||
+ | while(x>='0'&&x<='9'){num=num*10+x-'0';x=getchar();} | ||
+ | return num*f; | ||
+ | } | ||
+ | const int maxn=1000005; | ||
+ | char s[maxn]; | ||
+ | int n; | ||
+ | int b[maxn],a[maxn]; | ||
+ | int fir[maxn],sec[maxn],tmp[maxn],buc[maxn]; | ||
+ | int rnk[maxn],sa[maxn]; | ||
+ | void suf_sort(){ | ||
+ | int tot=n; | ||
+ | //对数据离散化,保证数据范围在[0,n]: | ||
+ | for(int i=1;i<=n;++i)b[i]=s[i]; | ||
+ | sort(b+1,b+tot+1); | ||
+ | tot=unique(b+1,b+tot+1)-b-1; | ||
+ | for(int i=1;i<=n;++i)a[i]=lower_bound(b+1,b+tot+1,s[i])-b; | ||
+ | //得到初始排序: | ||
+ | for(int i=1;i<=n;++i)++buc[a[i]]; | ||
+ | for(int i=1;i<=n;++i)buc[i]+=buc[i-1]; | ||
+ | for(int i=1;i<=n;++i)rnk[i]=buc[a[i]-1]+1; | ||
+ | |||
+ | for(int t=1;t<=n;t<<=1){ | ||
+ | //对第一和第二关键字进行初始化: | ||
+ | for(int i=1;i<=n;++i)fir[i]=rnk[i]; | ||
+ | for(int i=1;i<=n;++i)sec[i]=i+t>n?0:rnk[i+t]; | ||
+ | //对第二大关键字排序,tmp[i]保存第i大的关键字 | ||
+ | for(int i=0;i<=n;++i)buc[i]=0; | ||
+ | for(int i=1;i<=n;++i)++buc[sec[i]]; | ||
+ | for(int i=1;i<=n;++i)buc[i]+=buc[i-1]; | ||
+ | for(int i=1;i<=n;++i)tmp[n - --buc[sec[i]]]=i;//--buc[sec[i]]是一个[0,n)的值,减去它得到[1,n]的值 | ||
+ | //对第一关键字排序,按照tmp中的顺序领取排名,大的先领取便被放到了后面,完成了排序。 | ||
+ | for(int i=0;i<=n;++i)buc[i]=0; | ||
+ | for(int i=1;i<=n;++i)++buc[fir[i]]; | ||
+ | for(int i=1;i<=n;++i)buc[i]+=buc[i-1]; | ||
+ | for(int i=1;i<=n;++i){ | ||
+ | int t=tmp[i]; | ||
+ | sa[buc[fir[t]]--]=t; | ||
+ | } | ||
+ | //根据sa[]数组的顺序求出名次数组rnk[], 注意根据并列情况讨论 | ||
+ | bool uni=true; | ||
+ | for(int i=1,lst=0;i<=n;++i){ | ||
+ | int t=sa[i]; | ||
+ | if(!lst)rnk[t]=1;//第一个 | ||
+ | else if(fir[t]==fir[lst]&&sec[t]==sec[lst])rnk[t]=rnk[lst],uni=false;//和上一个相同 | ||
+ | else rnk[t]=rnk[lst]+1; //不同 | ||
+ | lst=t; | ||
+ | } | ||
+ | if(uni)break;//排序已经完成。 | ||
+ | } | ||
+ | for(int i=1;i<=n;++i)printf("%d ",sa[i]); | ||
+ | } | ||
+ | int main(){ | ||
+ | scanf("%s",s+1); | ||
+ | n=strlen(s+1); | ||
+ | suf_sort(); | ||
+ | |||
+ | return 0; | ||
+ | } | ||
+ | |||
+ | </code> |