后缀数组入门——初步理解及模板

后缀数组是跟着白书上面学的,感觉自己大概能理解了,写下来加深印象,以后慢慢学习。

所谓后缀就是从一个字符串某个位置开始到其末尾的字串(在KMP里也有这个概念)。包括了空串和该字符串本身。

后缀数组就是把所有的字符串后缀按照字典序排列后所得到的数组。在后缀数组的计算中,用到了以下几个关键数组。

rank用来记录字符串的排序,sa用来记录开头字符的位置,S用来记录字符串,S[i]表示第i个字符开头的后缀。

白书上的算法用的是倍增的思想。感觉和ST表很像。思想都是一样的。

因为如果你要直接对所有的后缀直接排序肯定超时。那么我们长度为n的字符串,能否通过已经处理过的长度为n/2的字符串排序呢?

每一次循环我们计算长度为k的字串的顺序,k每次乘以2.rank[i]就代表以第i个字符开头的长度为k的字串是第几小的。

如果我们想比较S[i,2*k]和S[j,2*k],分别代表以i和j开头的长度为2k的字串,那么我们是不是可以先比较,S[i,k]和S[j,k],如果相等,就比较S[i+k,k],S[j+k,k]。(因为是字典序比较,很好理解吧)


int n.k;
int rank[MAXN+1];
int tmp[MAXN+1];

//比较(rank[i],rank[i+k])和(rank[j],rank[j+k])
bool compare_sa(int i,int j){
	if(rank[i]!=rank[j])
		return rank[i]<rank[j];
	else{
		int ri=i+k<=n?rank[i+k]:-1;
		int rj=j+k<=n?rank[j+k]:-1;
		return ri<rj;
	}
}
//rank用来记录字符串的排序,sa用来记录开头字符的位置,S用来记录字符串
//第一个通常是空字符串
//计算字符串S的后缀数组
void construct_sa(string S,int *sa){
	n=S.lenth();

	//初始长度为1,rank直接取字符的编码.
	for(int i=0;i<=n;i++){
		sa[i]=i;
		rank[i]=i<n?S[i]:-1;
	}
	
	//利用对长度为k的排序的结果对长度为2k的排序
	for(k=1;k<=n;k*=2){
		sort(sa,sa+n+1,compare_sa);

		//先在tmp中临时储存新计算的rank,再转存回rank中
		tmp[sa[0]]=0;
		for(int i=1;i<=n;i++){
			tmp[sa[i]]=tmp[sa[i-1]]+(compare_sa(sa[i-1],sa[i])?1:0);
		}
		for(int i=0;i<=n;i++){
			rank[i]=tmp[i];
		}
	}
}



这样我们在进行字符串匹配的时候,就可以用二分搜索的方法匹配字符串了。效率是O(|T|log|S|)


bool contain(string S,int *sa,string T){
	int a=0;int b=S.lenth();
	while(b-a>1){
		int c=(a+b)/2;
		//比较S从位置sa[c]开始长度为|T|的字串
		if(S.compare(sa[c],T.lenth(),T)<0) a=c;
		else b=c;
	}
	return S.compare(sa[b],T.lenth(),T)==0;
}



大概思路就是这样。具体看代码吧。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值