后缀数组倍增算法代码及解释

经历

最近在学习后缀数组,我发现后缀数组那20多行代码10多个for,真的是太抽象了,基数排序又不是很会所以学起来异常艰难,在冥思苦想以及抱神犇(Dumpling)的大腿后终于有些理解了,将自己写的注释发出来帮助和我一样的人= =.(我觉得我对代码的讲解还是比较细了,但是我没有讲方法,只讲了代码,方法应该比较好懂.)

讲解

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=15000+10;
const char msg[]="ABCDEFG";

char s[maxn];
int sa[maxn],t[maxn],t2[maxn],c[maxn],n;

void build(int m)
{
	int i,*x=t,*y=t2;
	for(i=0;i<m;i++) c[i]=0;
	for(i=0;i<n;i++) c[x[i]=s[i]]++;
	for(i=1;i<m;i++) c[i] += c[i-1];
	for(i=n-1;i>=0;i--) 
	  sa[--c[x[i]]]=i;
	/*
	解读:c[x[i]]表示字符串中<=x[i]的字符个数,那么i应该在第 
	c[x[i]]个(同字符并不影响),因为sa从0开始,所以名次减
	一,但将这个拿走之后会少一个字符,c[x[i]]要-1,所以直接
	-- .即这一趟按单个字母排 . 
	*/
	for(int k=1;k<n;k<<=1) 
	{	//倍增,将k凑成k*2长度,k>=n就没必要了
		int p=0;
		for(i=n-1;i>=n-k;i--) y[p++]=i;
		/*
		#define G1 第一关键字 
		#define G2 第二关键字  
		这是一些长度不够的后缀,G2排序时一定会排
		在前面(G2全为'\0'(因为有'全'所以是n-k
		而不是n-k*2+1,倍增出来的G2是经过合并的)) 
		
		这里将n-k到n-1放进了y数组 
		*/ 
		for(i=0;i<n;i++) 
		  if(sa[i]>=k) y[p++]=sa[i]-k;
		/*
		合并过程中需要将G2往前移k个位置,那么y 
		(现在是按G2在排)中,y中G2的相对排名和原来的G2是一
		样的,所以直接复制过来,(也可以理解为sa并没
		有提供n-k~n-1这一段的有效信息,因为G2是空的) 
		这里将0~n-k-1放进了y数组
		
		综上,这两行将0~n-1不重不漏的放进了y数组. 
		*/
		 
		for(i=0;i<m;i++) c[i]=0;
		for(i=0;i<n;i++) c[x[y[i]]]++;
		for(i=1;i<m;i++) c[i]+=c[i-1];
		for(i=n-1;i>=0;i--) 
		  sa[--c[x[y[i]]]]=y[i];
		/*
		这4行对G2已经排好序的y进行G1排序,仍然是基数
		排序,不过因为G2变了(排单个字母时也可理解为
		G2全为空),不能再用i而要用y[i],想一想如果G2
		全空的话无论什么顺序都可以. 
		之所以要倒着加是保证按照G2已经排好的顺序,
		因为c[x[y[i]]]是相同G1里面名次最大的那个
		QAQ我终于懂了 
		*/
		
		swap(x,y);
		p=1;x[sa[0]]=0;//sa[0]是编号最小的那个后缀 
		for(i=1;i<n;i++)
		  if(y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k])
		    //G1==G'1&&G2==G'2 
			x[sa[i]]=p-1;
		  else x[sa[i]]=p++;
		if(p>=n) break;
		m=p;  
	}
	return ;
}

void debug(){
	for(int i=0;i<n;i++)
	{
		printf("%d ",i);
		for(int j=sa[i];j<n;j++)
		{
			printf("%c",s[j]);
		}
		printf("\n");
	}
	return ;
}

int main()
{
	scanf("%s",s);
	n=strlen(s);
	build('}'+1);
	debug();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值