算法问题描述
给定一个字符串s,按字典序排序s的所有子串
算法思想
鬼知道什么思想,好像没有什么思想。哦,想起来了,是倍增。
考虑最简单的后缀间
O(n)
O
(
n
)
比较和快排
O(nlogn)
O
(
n
l
o
g
n
)
,总复杂度
O(n2logn)
O
(
n
2
l
o
g
n
)
。考虑优化字符串间的比较,用倍增的思想,假设
k/2
k
/
2
长度的已经比完了,比较长度为
k
k
的字符串(长度不足补)只要比前
k/2
k
/
2
段再比较后
k/2
k
/
2
段,也就是把字符串当做一个二元组做双关键字排序,其中
k≪=1
k
≪=
1
。这样复杂度是
O(nlog2n)
O
(
n
l
o
g
2
n
)
的。考虑到我们是对字符串排序,字符串长度为
n
n
,则每个值的种类最多种,所以可以用基数排序。于是复杂度来到
O(nlogn)
O
(
n
l
o
g
n
)
。
细节
详见代码,细节太多
代码
void Qsort(){//对rak基数排序,结果记在sa里
for(int i=0;i<=m;i++) tax[i]=0; //清零
for(int i=1;i<=n;i++) tax[rak[tp[i]]]++; //第一关键字入桶
for(int i=1;i<=m;i++) tax[i]+=tax[i-1]; //记名次
for(int i=n;i;i--) sa[tax[rak[tp[i]]]--]=tp[i];//倒着取
//使用tp[i]的原因是我们进行的是双关键字排序,用基数排序的性质来理解就是我们初始数组的下标是tp[i],值是rak[tp[i]],基数排序具有稳定性,可以保证值相同时按下标排
//或者解释一下就是倒着取tp[i]可以使得第一关键字相同时第二关键字排后的先把桶里面大的名次取了用
//x=rak[tp[i]]:第二关键字排第i位的后缀在第一关键字中的值
//y=tax[x]--:取出来第一关键字的排名,下一个取的排名--
//sa[y]=tp[i]:第一关键字排名y的后缀是第二关键字排i的后缀
void Ssort(){//后缀排序
m=147;
Qsort();
for(int k=1,p=0;p<n&&k<=n;m=p,p=0,k<<=1){
//p是排名计数器(排名可能一样,当排名出现n种的时候就可以退出了)
for(int i=n-k+1;i<=n;i++) tp[++p]=i;
//后k个后缀不存在第二关键字,排最前
for(int i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k;
//sa是k=k/2时的sa,相当于第二关键字,如果有第二关键字,这个第二关键字的值就是现在sa[i]-k的
Qsort();
swap(rak,tp);//rak要重置,tp没用了,干脆用tp
rak[sa[1]]=p=1;//重置排名
for(int i=2;i<=n;i++){
rak[sa[i]]=(tp[sa[i-1]]==tp[sa[i]] && tp[sa[i-1]+k]==tp[sa[i]+k])?p:++p;
//基数排序不能重复,for比较相邻两个后缀是不是两个关键字都一样的
}
}
}
清爽版
void Qsort(){
for(int i=0;i<=m;i++) tax[i]=0;
for(int i=1;i<=n;i++) tax[rak[tp[i]]]++;
for(int i=1;i<=m;i++) tax[i]+=tax[i-1];
for(int i=n;i;i--) sa[tax[rak[tp[i]]]--]=tp[i];
void Ssort(){
m=147;
Qsort();
for(int k=1,p=0;p<n&&k<=n;m=p,p=0,k<<=1){
for(int i=n-k+1;i<=n;i++) tp[++p]=i;
for(int i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k;
Qsort();
swap(rak,tp);
rak[sa[1]]=p=1;
for(int i=2;i<=n;i++){
rak[sa[i]]=(tp[sa[i-1]]==tp[sa[i]] && tp[sa[i-1]+k]==tp[sa[i]+k])?p:++p;
}
}
}