这里先不讲后缀数组的应用,表示目前我也不怎么会,先讲讲怎么求后缀数组。
后缀数组是什么?对于一个长度为
n
n
n的字符串,显然,它有n个后缀。我们要做的就是把这
n
n
n个后缀按字典序排序,根据后缀独特的性质,我们可以做到
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)的时间复杂度。
特别的,定义
r
a
n
k
rank
rank数组,
r
a
n
k
[
i
]
rank[i]
rank[i]表示第
i
i
i个下标开始的后缀的排名,定义
s
a
sa
sa数组,
s
a
[
i
]
sa[i]
sa[i]表示排名为
i
i
i的后缀起始位置。显然这
n
n
n个后缀的长度不相同,所以不会有两个排名相同的后缀。
举个例子:ababa
rank:3,5,2,4,1
sa:5,3,1,4,2
说说怎么排名
看懂这张图了吗?嗯,我们现在会排序了。???
我们使用倍增的思想,先对第一位进行排序,然后两位一起排,以上一次排名的前一段作为第一关键字,后一段作为第二关键字。然后四位一起排,以此类推。似乎看代码比较好懂,那我们上代码吧!
在这之前还是先说一句,这里需要基数排序什么的,基数排序计数排序傻傻分不清
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
char s[1000005];//原字符串
int a[1000005];//字符串转数组
int rank[1000005],sa[1000005],tax[1000005],sec[1000005];/*rank[i]表示后缀i的排名;sa[i]表示排名为i的后缀位置;tax为基数排序的辅助数组;sec为排序的第二关键字,就是sa的魔改版,sec[i]表示排名为i的后缀向前数w个的位置*/
int len,m;//m为基数排序的最大值
void rsort()//基数排序
{
memset(tax,0,sizeof(tax));
for(int i=1;i<=len;i++) tax[rank[i]]++;
for(int i=1;i<=m;i++) tax[i]+=tax[i-1];
for(int i=len;i>=1;i--) sa[tax[rank[sec[i]]]--]=sec[i];/*从后往前遍历,可以使第一关键字相同时,第二关键字小的排在前面*/
}
void suffx()
{
for(int i=1;i<=len;i++)//最开始只有第一关键字,初始化rank和sec
rank[i]=a[i],sec[i]=i;
m=127;
rsort();
for(int p=0,w=1;p<len;w+=w,m=p)//p是用来计数的,当没有两个相同排名的数时就结束排序
{
p=0;
for(int i=len-w+1;i<=len;i++) sec[++p]=i;//假如某一个位置向后数w个没有后缀了,它第二关键字一定是在前面
for(int i=1;i<=len;i++)//把向后数w个有后缀的按sa排
if(sa[i]>w)
sec[++p]=sa[i]-w;
rsort();
swap(rank,sec);rank[sa[1]]=1;p=1;//把rank根据sa赋值,sec中暂时存一下rank
for(int i=2;i<=len;i++)
{
if(sec[sa[i]]==sec[sa[i-1]]&&sec[sa[i]+w]==sec[sa[i-1]+w])//如果两个以2w排名相同,rank要赋成相同值。
rank[sa[i]]=p;
else
rank[sa[i]]=++p;
}
}
}
int main()
{
scanf("%s",s);
len=strlen(s);
for(int i=0;i<len;i++)
a[i+1]=s[i];
suffx();
for(int i=1;i<=len;i++)
{
printf("%d ",sa[i]);
}
}