后缀数组

后缀数组


定义

后缀是什么就不用说了吧…

在后缀数组中,我们把从第i个下标开始的后缀称为第i个后缀(我个人比较习惯字符串下标从1开始)

这样一来我们得到n个后缀

通过倍增的思想,我们可以再 O ( n l o g n ) O(nlogn) O(nlogn)的时间内对这n个后缀按照字典序排序

变量名:

  • sa[i]:表示排名i的后缀是第几个后缀
  • rk[i]表示第i个后缀的排名是多少
  • height[i]表示sa[i]sa[i-1]最长公共前缀(LCP)

举个例子:

字符串aababb的后缀分别是:

  1. aababb
  2. ababb
  3. babb
  4. abb
  5. bb
  6. b

第一次我们以这些后缀的第一个字符作为关键字排序

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-57UGFAE3-1614075926050)(C:\Users\Typedef\Pictures\笔记\屏幕截图 2021-02-21 082349.png)]

排完结果是:

  • aababb
  • ababb
  • abb
  • babb
  • bb
  • b

通过基数排序我们可以在 O ( n ) O(n) O(n)时间内排好序

每次我们考虑的字符是倍增的

若我们已经按前k个字符排好序

那么我们以前k个字符作为第一关键字,k~2k个字符为第二关键字进行基数排序

这样我们就以前2k个字符排好序了

具体来说,每次排好序后,我们把排序的前k个字符进行离散化,这样以离散化的结果为第一关键字进行下一轮

l o g ( n ) log(n) log(n)轮之后就求出来了

实现细节:

:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zXCEZ0ku-1614075926052)(C:\Users\Typedef\Pictures\笔记\基数.png)]

基数排序过程中,我们用桶存下每个数出现的次数

然后对桶过一遍前缀和,得到一个类似每个数的排名的一个数组

为保障排序的稳定性,我们在原数组中逆序更新排名,更新排名的同时还要更新前缀和数组

最终在 O ( n ) O(n) O(n)的时间内得到每个数的排名

那么如何进行双关键字的排序呢?

我们首先按照第二关键字进行基数排序

然后再按照第一关键字对排序结果进行排序

OK

那么我们怎么得到第二关键字呢?

观察发现.第i个后缀的第二关键字其实就是第i+k个后缀的第一关键字

这样我们可以顺序求出第二关键字,注意考虑边界

height数组怎么求呢?

补充几个LCP的性质(i,j,k按照字典序排序,且rk[i]<rk[k]<rk[j])

  • l c p ( i , j ) = l c p ( j , i ) lcp(i,j)=lcp(j,i) lcp(i,j)=lcp(j,i)
  • l c p ( i , i ) = l e n ( i ) lcp(i,i)=len(i) lcp(i,i)=len(i)
  • l c p ( i , k ) = l c p ( k , j ) lcp(i,k)=lcp(k,j) lcp(i,k)=lcp(k,j)
/*************************************************************************
    > File Name: p3809[模板]后缀排序.cpp
    > Author: typedef
    > Mail: 1815979752@qq.com 
    > Created Time: 2021/2/23 7:43:45
 ************************************************************************/
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+7;
int n,m;
char s[N];
int sa[N],x[N],y[N],c[N],rk[N],height[N];//x是第一关键字,y是第二关键字,c是关键字的个数
void get_sa(){
	for(int i=1;i<=n;i++) c[x[i]=s[i]]++;
	for(int i=2;i<=m;i++) c[i]+=c[i-1];
	for(int i=n;i;i--) sa[c[x[i]]--]=i;
	for(int k=1;k<=n;k<<=1){
		int num=0;
		for(int i=n-k+1;i<=n;i++) y[++num]=i;
		for(int i=1;i<=n;i++)
			if(sa[i]>k) y[++num]=sa[i]-k;
		for(int i=1;i<=m;i++) c[i]=0;
		for(int i=1;i<=n;i++) c[x[i]]++;
		for(int i=2;i<=m;i++) c[i]+=c[i-1];
		for(int i=n;i;i--) sa[c[x[y[i]]]--]=y[i],y[i]=0;
		swap(x,y);
		x[sa[1]]=1,num=1;
		for(int i=2;i<=n;i++)
			x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
		if(num==n) break;
		m=num;
	}
	return;
}
void get_height(){
	for(int i=1;i<=n;i++) rk[sa[i]]=i;
	for(int i=1,k=0;i<=n;i++){
		if(rk[i]==1) continue;
		if(k) k--;
		int j=sa[rk[i]-1];
		while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k]) k++;
		height[rk[i]]=k;
	}
}
int main(){
	scanf("%s",s+1);
	n=strlen(s+1),m=122;
	get_sa();
//	get_height();
	for(int i=1;i<=n;i++) printf("%d ",sa[i]);
	puts("");
//	for(int i=1;i<=n;i++) printf("%d ",height[i]);
//	puts("");
	return 0;
}
posted @ 2021-02-23 18:26  actypedef  阅读(35)  评论(0编辑  收藏  举报