后缀排序——精选推荐

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

后缀排序
后缀数组是⼀个思路较为清晰,代码⼗分⽞学的操作,建议⼤家按照代码模拟⼀下样例,理解每⼀步操作的意义后缀数组的作⽤是将长度为N的字符串的N个后缀来进⾏排序
我们直接排序的复杂度是
后缀数组常⽤⽅法是倍增+基数排序算法:
1.基数排序
我们先来看⼀下代码:(默认升序排列)
//rep(i, a, b)是正序从a-b枚举
//drep(i, a, b)是倒序从a-b枚举
//a数组为基数排序辅助数组,即为⼀个桶
//rk数组为基数排序的第⼀关键字
//tp第⼆关键字中,排名为i的数的位置
//sa数组可以在此处理解为排完序后排名为i的数对的位置
il void Qsort() {
rep(i, 1, m) a[i] = 0;//
rep(i, 1, n) ++ a[rk[i]];
rep(i, 1, m) a[i] += a[i - 1];
//记录前缀和以后,a[i]的意义是rak为i的数最⼤可以到哪⾥
drep(i, 1, n) sa[a[rk[tp[i]]] --] = tp[i];
//-- a的意义是减少了⼀个位置,所以a最⼤可以到的位置往前移⼀个
}
我们来模拟⼀下,假设我们要对⼀个数组进⾏基数排序
其中第⼀关键字为:
对应第⼆关键字为:
所以对应数组为:
第⼀步
我们清空桶
第⼆步
我们将第⼀关键字压⼊桶中,得到a数组:
第三步
我们将a数组记录前缀和,得到a数组:
然后我们就可以发现⼀个奇妙的性质
对与第⼀关键字为1的数对,他们的排名为
对与第⼀关键字为2的数对,他们的排名为
对与第⼀关键字为3的数对,他们的排名为
对与第⼀关键字为4的数对,他们的排名为
所以从某种意义上来说,我们已经对第⼀关键字排好了序
第四步
我们从前往后倒序枚举
⾸先,最后⼀个数对的第⼆关键字为8,第8个数对的第⼀关键字为2,2的桶现在为5,所以排名为5的位置是第8个然后2的桶减⼀,因为第五个位置已经被占⽤,所以第⼀关键字为2的数对排名为
第7个数对的第⼆关键字为6,第六个数的第⼀关键字的桶为7个,于是排名为7的位置是第6个
以此类推,我们可以拍好序,最后的sa数组为
2.倍增
定义:
为以a为第⼀关键字,b为第⼆关键字进⾏基数排序)
表⽰原数组的第i位字符
后缀表⽰对字符串S的每个后缀,取左边i个字符,得到⼀个i-后缀
表⽰第j位上的i-后缀的排名
操作:
我们可以先按进⾏基数排序,得到
再按照进⾏基数排序,得到
因为是倍增,所以我们可以通过进⾏基数排序,得到
同理,我们也可以⽤进⾏基数排序,得到
当所有互不相同,排序结束
代码如下(倍增部分代码有详细注释,就不模拟了,各位最好⼿动模拟,理解每⼀⾏代码,注意在模拟的时候分清楚rk[i]和sa[i]的区别,⼀定要先清楚每⼀个数组的意义):#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<cstdlib>
using namespace std;
#define il inline
#define re register
#define debug printf("Now is Line : %d\n",__LINE__)
#define file(a) freopen(#a".in","r",stdin);freopen(#a".out","w",stdout)
il int read() {
re int x = 0, f = 1; re char c = getchar();
while(c < '0' || c > '9') { if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
#define rep(i, s, t) for(re int i = s; i <= t; ++ i)
#define drep(i, s, t) for(re int i = t; i >= s; -- i)
#define _ 1000005
int n, m, a[_], sa[_], rk[_], tp[_];
char c[_];
/*
sa[i]:排名为i的后缀的位置
rk[i]:第i个位置开始的后缀的排名,作为基数排序的第⼀关键字
即sa[rk[i]] = rk[sa[i]] = i
tp[i]:第⼆关键字中,排名为i的数的位置
a[i]:有多少个元素排名为i
c[i]:原输⼊数组
*/
il int get(char c) {
if(c >= '0' && c <= '9') return c - '0' + 1;
if(c >= 'A' && c <= 'Z') return c - 'A' + 11;
return c - 'a' + 37;
}
il void init() {
rep(i, 1, n) rk[i] = get(c[i]), tp[i] = i;
}
il void print() {
rep(i, 1, n) printf("%d ", sa[i]);
}
il void Qsort() {
rep(i, 1, m) a[i] = 0;
rep(i, 1, n) ++ a[rk[i]];
rep(i, 1, m) a[i] += a[i - 1];
drep(i, 1, n) sa[a[rk[tp[i]]] --] = tp[i];
}
il void get_sort() {
for(re int w = 1, p = 0; p < n && w <= n; m = p, p = 0, w <<= 1) {
//p在此时的意义是最⾼出现的排名以及⼀个计数器
//p < n的意义是如果当前最⼤排名== n则⽆继续排序的意义
rep(i, n - w + 1, n) tp[++ p] = i;
//这⾥p的定义只是⼀个计数器
//tp[i]表⽰第⼆关键字中,排名为i的数的位置
//因为i - w + 1后⾯没有第⼆关键字,所以要补0
//所以要设成极⼩值排在前⾯
rep(i, 1, n) if(sa[i] > w) tp[++ p] = sa[i] - w;
//在第⼀关键字中排名越靠前,表⽰应该排在前⾯
Qsort(), swap(rk, tp), p = rk[sa[1]] = 1;
//我们现在更新rk,tp已经⽆⽤,先备份,⽤memcpy也⾏
//注意到⼀个性质 : rk[sa[i]] = i
rep(i, 2, n) rk[sa[i]] = (tp[sa[i]] == tp[sa[i - 1]] && tp[sa[i] + w] == tp[sa[i - 1] + w]) ? p : ++ p;
//注意tp和rk已经交换,所以这个判断的意思是:
//如果两个后缀还相等,则排名不变,否则++
}
}
int main() {
scanf("%s", c + 1), n = strlen(c + 1), m = 62;
init(), Qsort(), get_sort(), print();
return 0;
}。

相关文档
最新文档