SA(后缀数组)学习笔记
SA(后缀数组)学习笔记
约定
- 下标显示出来太小了,于是可能会用中括号代替下标。
- 定义
为 与 这两个字符串的拼接。 - 定义
。 - 定义
,也就是 的后缀。 - 下文的
表示 的串长。 - 将后缀按字典序排序,定义
为排名为 的后缀开头的下标, 为 的排名,注意不要混淆。
正篇
一、快排
我们比较两个字符串的大小复杂度为
二、哈希
快排的复杂度太不给力了。我们试图优化一下。
我们试图从比较两个字符串的部分下手。采用二分哈希可以找到两个字符串第一个不相同的位置,就可以比较了。复杂度
三、倍增
两
我们对每个后缀从第一个字符开始考虑,也就是将第一个字符看做第一关键字。那么,这个时候我们就能直接做一个快排。但是如果两个后缀的第一关键字相同,我们就需要考虑第二个,第三个甚至更多。这样的最坏复杂度仍然是
但是我们还能优化。考虑我们在比完第二个字符时,我们将第一二个字符看做第一关键字,第三四个字符看做第二关键字,那么此时第二关键字的相对大小也已经被算了出来,因为它们同时是另一个后缀的第二关键字。
继续往下做,发现是一个倍增的过程。那么我们快排的总次数就是
四、基排
好吧,可能是快排不太给力。有没有什么更快一点的排序呢?
答案是有的。考虑基数排序。我们对一堆两位数排序,可以先按照第二关键字排序,得到一个初始顺序,然后再按照这个初始顺序再次进行排序,也就是不破坏第一关键字相同的值的相对顺序。这样就保证了第二关键字的有序。
两次排序用桶排实现。为什么不直接桶排?值域太大,桶排复杂度是
将上面的过程搬到后缀排序的过程中来,我们就得到了一个
说的可能比较抽象,可以结合代码理解:
#include<bits/stdc++.h>
using namespace std;
char s[1000010];
int n,m,cnt;
int bu[1000010],k1[1000010],p[1000010],sa[1000010];
int main()
{
scanf("%s",s+1);
n=strlen(s+1);
m='z';
for(int i=1;i<=n;i++) ++bu[s[i]],k1[i]=s[i];
for(int i=1;i<=m;i++) bu[i]+=bu[i-1];
for(int i=1;i<=n;i++) sa[bu[s[i]]]=i,--bu[s[i]];
for(int k=1;k<=n;k<<=1){
cnt=0;
for(int i=n-k+1;i<=n;i++) p[++cnt]=i;
for(int i=1;i<=n;i++){
if(sa[i]>k) p[++cnt]=sa[i]-k;
}
memset(bu,0,sizeof(bu));
for(int i=1;i<=n;i++) ++bu[k1[i]];
for(int i=1;i<=m;i++) bu[i]+=bu[i-1];
for(int i=n;i>=1;i--) sa[bu[k1[p[i]]]]=p[i],--bu[k1[p[i]]];
for(int i=1;i<=n;i++) p[i]=k1[i],k1[i]=0;
k1[sa[1]]=1,m=1;
for(int i=2;i<=n;i++){
if(p[sa[i]]==p[sa[i-1]]&&p[sa[i]+k]==p[sa[i-1]+k]) k1[sa[i]]=m;
else k1[sa[i]]=++m;
}
if(m==n) break;
}
for(int i=1;i<=n;i++){
printf("%d ",sa[i]);
}
return 0;
}
五、LCP
SA 的应用之一。有时,我们需要
定义
-
。 -
。
这两条非常显然。
- LCP Lemma:
。
证明:设
则
所以
我们假设
由于
这时
因此
- LCP Theorem:
。
证明:当
当
我们从 LCP Theorem 中发现如果求出了所有
。
证明:设
当
设
我们有
又因为
由于
根据 LCP Lemma,我们有
有了这一条性质,我们就可以设计出一个简单的均摊
for(int i=1;i<=n;i++){
if(rk[i]==1) continue;//h[1] 没有意义
f=max(h[rk[i-1]]-1,0),j=sa[rk[i]-1];
while(j+f<=n&&i+f<=n&&s[i+f]==s[j+f]) ++f;
h[rk[i]]=f;
}
感谢阅读!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效