Suffix Array【后缀数组】

算法介绍


  • 后缀数组是用来解决⼀类字符串问题的⼯具 
  • 算法目标是计算出字符串的每个后缀的排名
  • 几个要用到的定义
    • n       : 字符串的长度
    • sz     : 字符集
    • s       : 字符串,方便起见,我们从0开始标号,即 s[0]~s[n-1]
    • suf[i] : 下标从i开始的后缀
    • rk[i]   : 以 s[i] 开头的后缀的排名为rk[i]
    • sa[i]  : 即排名为i的后缀从sa[i]开始
  • 原理
    • 桶排
    • 按照2的幂次倍增
    • 将后缀分成前后两个等长的部分,先将所有后缀按照前半部分排序,完成后 再将后半部分考虑进去,两部分结果合并完成最终的排序
    • 如下图

 

最长公共前缀


  • rk[i]表示后缀 i 的排名,height[i]表示 排名为i的和排名为i-1的LCP长度。对于两个前缀 j 和 k,j>k,不妨设rk[j]

Suffix Array【后缀数组】_第1张图片

  • 根据定义,每次计算一个height数组,都需要O(n),则共需要O(n^2),还可以进行优化
  • 再用个辅助数组h[i]=height[rank[i]],然后按照h[1],h[2]……h[n]的顺序递推计算, 递推的关键在于这样一个性质:h[i]>=h[i-1]-1
int getheight() {
    int k=0;
    rep(i,0,n-1) rk[sa[i]]=i;
    rep(i,0,n-1) {
        if (rk[i]==0) continue;//第一名height为0
        if (k) --k;//h[i]>=h[i-1]-1;
        int j=sa[rk[i]-1];
        while (j+k<=n && i+k<=n && s[i+k]==s[j+k]) ++k;
        h[i]=height[rk[i]]=k;//h[i]=height[rk[i]];
    }
}
  • 证明
    • 设排在后缀i-1前一个的是后缀 k,后缀 k 和后缀 i-1 分别删除首字符之后得到后缀 k+1和后缀 i,因此后缀 k+1一定排在后缀 i 的前面,并且它们的最长公共点缀长度为h[i-1]-1,如图所示:

Suffix Array【后缀数组】_第2张图片

  • 这个h[i-1]-1是一系列h值的最小值,这些h值包括后缀i和排在它前一个的后缀p的LCP长度,即h[i],因此h[i]>=h[i-1]-1。

Code


void SA(){
	int sz=max(n,300);
	rep(i, 0,   n-1 )	rk[i]=s[i];
	rep(i, 0,   n-1 ) 	++bin[rk[i]];
	rep(i, 1,   sz-1)	bin[i]+=bin[i-1];
	rep(i, 0, n-1  ) 	sa[--bin[rk[i]]]=i;
	for(int j=1;j<=n;j*=2)
	{
		int p=0;
		per(i,	n-1, n-j )   tmp[p++]=i;
		rep(i,	0,	 n-1 )	 if(sa[i]-j>=0)tmp[p++]=sa[i]-j;//Attention
		rep(i,	0,	 sz-1) 	 bin[i]=0;
		rep(i,	0,	 n-1 )   ++bin[rk[i]];
		rep(i,	1,	 sz-1) 	 bin[i]+=bin[i-1];
		per(i,	n-1, 0   ) 	 sa[--bin[rk[tmp[i]]]]=tmp[i];//Attention
		p=tmp[sa[0]]=0;
		rep(i,1,n-1){
			int v0=sa[i-1],v1=sa[i],v00,v01;
			if (v0 + j < n) v00 = rk[v0 + j]; else v00 = -1 ;//Attention
        	        if (v1 + j < n) v01 = rk[v1 + j]; else v01 = -1 ;//Attention
			if(rk[v0]==rk[v1] && v00==v01) tmp[v1]=p;//Attention
			else tmp[v1]=++p;
		}
		rep( i, 0, n-1  )    rk[i]=tmp[i];
	}
}
int getheight() {
    int k=0;
    rep(i,0,n-1) rk[sa[i]]=i;
    rep(i,0,n-1) {
        if (rk[i]==0) continue;
        if (k) --k;
        int j=sa[rk[i]-1];
        while (j+k

练习


  • UOJP35后缀排序
  • POJ2774 LongLongMessage
  • SPOJ - SUBST1 New Distinct Substrings
  • USACO 2006 December Milk Patterns

 

鸣谢


  • https://www.cnblogs.com/jinkun113/p/4743694.html

你可能感兴趣的:(字符串)