倍增求后缀数组
倍增求后缀数组
1. 一些定义
后缀
后缀数组即
2. 求法
先对每个单独的字符从小到大排序,得到每个字符的排名和后缀数组,记排名为
再把
以
这样可以得到每个长度为
再把
以
这样可以得到每个长度为
以此类推,对于长度
以
这样可以得到每个长度为
一直倍增直到
这样时间复杂度的瓶颈在于
考虑到排名的值域为
这样总时间复杂度就为
3. 基数排序
对于有
每次对当前关键字做一次稳定的排序,便可完成整个排序。
一般选组计数排序作为【稳定的排序】,时间复杂度:
这样为什么是对的呢?
对于第
由于第
如果第
4. 实现细节
在倍增时,可能会出现相同的字符串,这时它们的排名必须相等。
所以处理排名时还需要一个去重操作。
还有一个优化,如果某次处理时已经没有重复的字符串,
那最终排名和后缀数组都已确定,结束整个算法就行,实测优化很大。
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
int n, sa[N], rk[N << 1]; // 避免 i + w 越界
char S[N];
struct node {int key[2], id;};
node a[N], b[N];
int cnt[N];
void sort(int p, int w) { // 计数排序
memset(cnt, 0, sizeof(cnt));
for (int i = 1; i <= n; i ++) cnt[a[i].key[p]] ++;
for (int i = 1; i <= w; i ++) cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; i --) b[cnt[a[i].key[p]] --] = a[i];
for (int i = 1; i <= n; i ++) a[i] = b[i];
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin >> (S + 1);
n = strlen(S + 1);
for (int i = 1; i <= n; i ++) rk[i] = S[i]; // 第一遍排序
for (int w = 1; w <= n; w <<= 1) {
int maxw = 0;
for (int i = 1; i <= n; i ++) // i 为第一关键字, i + w 为第二关键字
a[i] = {{rk[i], rk[i + w]}, i},
maxw = max(maxw, rk[i]);
sort(1, maxw); sort(0, maxw); // 第二关键字先排序, 第一关键字后排序
int p = 0;
for (int i = 1; i <= n; i ++) {
if (a[i].key[0] == a[i - 1].key[0] // 去重操作
&& a[i].key[1] == a[i - 1].key[1]) rk[a[i].id] = p;
else rk[a[i].id] = ++ p;
sa[i] = a[i].id; // 统计后缀数组
}
if (p == n) break; // 没有重复字符串, 优化
}
for (int i = 1; i <= n; i ++) cout << sa[i] << " ";
return 0;
}
本文来自博客园,作者:maniubi,转载请注明原文链接:https://www.cnblogs.com/maniubi/p/18518909,orz
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】