【理论】后缀数组 I —— 后缀排序
后缀数组(suffix array)是省选字符串题目中非常重要的算法。
本文将简略讲述其
考虑一种字符串比较大小的新方式。
对于长度为
这种比较方式的正确性很显然,但我们要考虑其带给我们的启示。
我们这个比较方式实际上将一个大的问题分解为了两个相对较小的问题,这实际上是一种分治。
那么,基于这种分治思想的后缀排序便应运而生了。
其采用分治的思路,使用倍增进行具体的实现(先解决
由于在后缀排序中我们需要比较多个字符串,所以我们需要思考如何表示多个字符串大小关系,我们可以采用排名的方式,记录每个字符串排序后在有序的字符串序列中的位置,以此作为大小关系的表示方式,特别的,当两个字符串相等时,其排名也相等。
后缀排序的过程如下:
设字符串
使用后缀数组进行长度为
在比较
-
先比较
和 的大小,若 则比较结果直接可得出。 -
若
则再继续比较 和 的大小。
需要注意的是,如果经过两次比较后仍相同,它们的排名也需保持相同。
很多人在了解全过程后会有疑问,求的应该是后缀的排序,但过程中只有对所有长度为
事实上,最后一次倍增时有
由此,后缀数组的过程便完成了,但如何在每一次倍增时根据
观察一下多个字符串比较的本质,我们发现其本质上为一个双关键字排序。
考虑朴素做法,即暴力
那么可以想见我们需要一种更快的排序,能够在
我们选择使用基数排序。
我们将一个包含双关键字的字符串看做一个二元组。
下面简述基数排序的过程:
但我们发现,如此排完一次序后,所有二元组的排名将互异,但却存在两个二元组(字符串)相同的情况,怎么办?
考虑判断排序后相邻二元组是否相等,即赋排名时若该二元组与上一个二元组相同,则排名等于上一个二元组的排名,否则排名等于上一个二元组的排名
后缀排序问题成功解决。
代码
void sufsrt(){
rep(i,1,n) ++a[(ll)s[i]];
rep(i,1,200) a[i]+=a[i-1];
rep(i,1,n) sa[a[(ll)s[i]]--]=i;
rk[sa[1]]=1;
rep(i,2,n){
rk[sa[i]]=rk[sa[i-1]];
if(s[sa[i]]==s[sa[i-1]]) continue;
++rk[sa[i]];
}
rep(i,0,n){
rep(j,1,n) a[j]=0;
rep(j,1,n) ++a[rk[f[i][j]]];
rep(j,1,n) a[j]+=a[j-1];
rep(j,1,n) tp[a[rk[f[i][j]]]--]=j;
rep(j,1,n) a[j]=0;
rep(j,1,n) ++a[rk[j]];
rep(j,1,n) a[j]+=a[j-1];
repp(j,n,1) sa[a[rk[tp[j]]]--]=tp[j];
nw[sa[1]]=1;
rep(j,2,n){
nw[sa[j]]=nw[sa[j-1]];
if(rk[sa[j]]==rk[sa[j-1]]&&rk[f[i][sa[j]]]==rk[f[i][sa[j-1]]]) continue;
++nw[sa[j]];
}
rep(j,1,n) rk[j]=nw[j];
if((1<<i)>=n) break;
}
return;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理