后缀数组

题目很简单,就是给出一个字符串,把这个字符串的所有非空后缀从小到大排序后,按顺序输出后缀的第一个字符在原串中的位置。

样例

输入样例:

ababa

输出样例:

5 3 1 4 2

解释:

排好序后为:

  1. a

  2. aba

  3. ababa

  4. ba

  5. baba

暴力肯定不行啦!
这里我们使用一个叫后缀数组的东西

PS:搞懂模版还要感谢大佬zhangjianweivv的博客!

什么是后缀数组?

后缀数组为rank[i],为Suffix array的简写,表示排名为i的字符串的 编号 (PS:这里,我们简称排序后后缀的第一个字符所在的位置为 编号

还有一个玩意儿叫名次数组,为rank[i],表示编号为i的字符串的 排名

可以发现,rank[i]sa[i]的逆运算,而且我们所需要的答案就是sa[i]

后缀数组怎么求?

这才是真正的步入主题啊。。ps:前面讲辣么多废话

这里我们讲倍增算法,因为好理解我菜

另当前的字符串为aabaaaab

下面这个图很清楚:

图片来源:《后缀数组–处理字符串有力工具》

  • 我们先把字符串的每个字母排序,存在rank数组里面,我们认为这是第一关键字

  • 然后我们将第一关键字每隔 2 k 2^k 2k的字符的排名作为第二关键字

  • 这时,我们使用计数排序(网上很多都说是基数排序,但其实是计数排序)将这两个关键字排序,就类似于两位数字的排序

  • 将排序后的结果作为第一关键字,然后继续循环第二,三次操作

计数排序是什么?

计数排序和基数排序很像

先讲基数排序:

图片来源:jinkun113大佬的博客

类似于桶排序,

  • 先按个位的数字一个个放入09的桶里,再按顺序从09取出来,(当然如果你是从大到小就是9~0啦!)

  • 接着再按十位的数字一个个放入桶里,按顺序取出来,如果是两位数,这时我们得到的就是答案!

  • 以此类推,一直循环到最后一位

计数排序,就更高级了。这里优化了一下,
就是我们在基数排序所用的桶,是记录他出现的次数,然后累加(写入代码就是cnt[i]+=cnt[i-1]),这样我们得到的cnt[i]数组里就是不大于i的个数,就是他的排名了!

上代码!

我先解释一下每个变量的含义

rank[i]表示编号为i的排名 
sa[i]表示排名为i的编号 
cnt[i]计数排序的桶 
pos[i]表示当前第二关键字已经排好序时第i名第二关键字所对应的第一关键字位置
tmp[i]
排序时:表示当前排序中编号为i的排名
排序后:表示调整rank前的排名
#include<cstdio>
#include<cstring>
using namespace std;
/*
rank[i]表示编号为i的排名 
sa[i]表示排名为i的编号 
cnt[i]计数排序的桶 
pos[i]表示当前第二关键字已经排好序时第i名第二关键字所对应的第一关键字位置
tmp[i]
排序时:表示当前排序中编号为i的排名
排序后:表示调整rank前的排名
*/
char s[1200000];
int cnt[1200000],pos[1200000],sa[1200000],tmp[1200000],rank[1200000];
bool pd(int x,int y,int k){return tmp[x]==tmp[y]&&tmp[x+k]==tmp[y+k];}
void suffix(int n,int m)
{
	int i,k;
	for(i=1;i<=n;i++)rank[i]=s[i],cnt[rank[i]]++;
	for(i=1;i<=m;i++)cnt[i]+=cnt[i-1];
	for(i=n;i>=1;i--)sa[cnt[rank[i]]--]=i;//计数排序,设置好rank和sa数组 
	for(k=1;k<n;k<<=1)//k表示长度,k<<1表示k*2 
	{
		int len=0;for(i=n-k+1;i<=n;i++)pos[++len]=i;//如果第二关键字为0的话,肯定最小,我们先加入pos 
		for(i=1;i<=n;i++)if(sa[i]>k)pos[++len]=sa[i]-k;//准备好pos数组 
		
		memset(cnt,0,sizeof(cnt));
		for(i=1;i<=n;i++)tmp[i]=rank[pos[i]],cnt[tmp[i]]++;
		for(i=1;i<=m;i++)cnt[i]+=cnt[i-1];
		for(i=n;i>=1;i--)sa[cnt[tmp[i]]--]=pos[i];//更新sa数组 
		
		for(i=1;i<=n;i++)tmp[i]=rank[i];//记录之前的排名 
		
		len=1;rank[sa[1]]=1;//初始化 
		for(i=2;i<=n;i++){if(pd(sa[i],sa[i-1],k)==false)len++;rank[sa[i]]=len;}//通过sa来更新rank数组,并且去重 
		if(len==n)break;m=len; 
	}
	for(i=1;i<n;i++)printf("%d ",sa[i]);
	printf("%d\n",sa[n]);
}
int main()
{
	scanf("%s",s+1);int len=strlen(s+1);
	suffix(len,130);//'z'是122 ,为了保险设置为130
	return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值