您的位置:首页 > 理论基础 > 数据结构算法

【数据结构】后缀数组模板+代码理解

2017-05-18 09:05 441 查看
后缀数组

SA[i]:
第i名是谁。它保存 1..n 的某个排列 SA[1] ,SA[2],……,SA
,并且保证 Suffix(SA[i]) < Suffix(SA[i+1]),1≤i<n 。也就是将 S 的 n 个后缀从小到大进行排序之后把排好序的后缀的开头位置顺次放入 SA 中。
Rank[i]:第i位是第几名。名次数组 Rank[i]保存的是 Suffix(i)在所有后缀中从小到大排列的“名次 ” 。

具体可以参考国家集训队论文《后缀数组——处理字符串的有力工具》 



int wa[maxn],wb[maxn],wv[maxn],ws[maxn];
int cmp(int *r,int a,int b,int l)
{
return r[a]==r&&r[a+l]==r[b+l];
}
//待排序的字符串放在r数组中,从r[0]到r[n-1],长度为n,除r[n-1]外都大于0,r[n-1]=0。函数结束后结果放在sa数组中,从sa[0]到sa[n-1]
void da(int *r,int *sa,int n,int m)
{
int i,j,p,*x=wa,*y=wb,*t;
for(i=0;i<n;i++) ws[i]=0;
for(i=0;i<n;i++) ws[x[i]=r[i]]++;
for(i=0;i<m;i++) ws[i]+=ws[i-1];
for(i=n-1;i>=0;i--) sa[--ws[x[i]]]=i;
//以上为第一次基数排序,对长度为1的字符串进行排序,得到基数排序的第一关键字
for(j=1,p=1;p<n;j*=2,m=p)//j代表代表每一小段字符串的长度,从1开始,每次倍增.
{
for(p=0,i=n-j;i<n;i++) y[p++]=i;//第二关键字可以由上一次的rank数组获得,观察图可知,从n-j位开始往后的第二关键字都为0,相对的它们的排位是最低的.
for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;//从排名最低的开始,如果i的位置大于字符串长度j,说明它对应sa[i]-j这个位置的第二关键字,同样根据上图可知。
for(i=0;i<n;i++) wv[i]=x[y[i]];//通过排好序的第二关键字,将对应的值放到wv[i]中。
for(i=0;i<m;i++) ws[i]=0;//开始第一关键字的排序 ,和对长度为1的字符串进行排序一样.
for(i=0;i<n;i++) ws[wv[i]]++;
for(i=0li<m;i++) ws[i]+=ws[i-1];
for(i=n-1;i>=0;i--) sa[--ws[wv[i]]]=y[i];
for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++)//计算rank值,可能有多个字符串的rank值相同(在还未完全排好序之前,总是有相同的rank存在),所以必须比较两个字符串是否完全相同。保留第二关键字的y数组已经没用,这里跟x互换,用来保存rank值。
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++; //交换后的x数组用来储存rank值, 使用cmp判断当前字符串跟之前的字符串是否完全相同,如果相同,就赋给它相同的值p-1,否则就赋值为新的rank并将p+1
}
return ;
}
[b]height 数组
:
定义height[i]=suffix(sa[i-1])和suffix(sa[i])的最长公共前缀,也就是排名相邻的两个后缀的最长公共前缀.

h[i]数组:

定义h[i]=height[rank[i]],suffix(i)和它前一名的后缀的最长公共前缀,可以证明h[i]>=h[i-1]+1.利用这个性质,我们可以更快求出height数组

int rank[maxn],height[maxn];
void calheight(int *r,int *sa,int n)
{
int i,j,k=0;
for(i=1;i<=n;i++)rank[sa[i]]=i;
for(i=0;i<n;height[rank[i++]]=k)
for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++)
/*上面两个for的理解
第一层循环将遍历i,并且在内层循环结束后将k赋给height[rank[i]],也就是所谓的h[i]
第二层循环初始化k,k一开始为0。j初始化为排名前一位的下标.因为h[i]>=h[i-1]+1,那么k的意思就是我应该从第k位开始比较。
当前位的下标i跟排名前一位的下标j,如果r[i+k]==r[j+k],说明它们之后第k位仍然相等,就将k加1继续判断。由于这个原因,k之后有可能
不为0,那么在初始化的时候,k会减去1,即从这个位置继续比较。因为之前的h[i]和h[i-1]比较到了第k位才不匹配,那么h[i+1]和h[i]的前k-2位其实都是相等的。
我只要从第k-1位继续比较即可。
*/
return ;
}

应用:

①求一个串中两个子串的最大公共前缀(即height数组)

②一个串中可重叠的最长重复子串(height数组的最大值)

③一个串中不可重叠的最长重复子串(二分答案,转换成判断是否存在两个长度为k的子串是相同的,且不重叠。利用Height数组进行分组,每一组之间的height值都不小于k,那么满足条件的一定在同一组。那么对于每一组,我们只需要判断每个后缀的sa最大值跟最小值之差是否不小于k,有一组满足则存在。这里的分组思想很重要!)

int deal(int n,int p)
{
int minx=sa[0],maxx1=sa[0];
for(int i=0;i<=n;i++)
{
if(height[i]>=p)
{
if(minx>sa[i])
minx=sa[i];
if(maxx1<sa[i])
maxx1=sa[i];
if(maxx1-minx>p)
return 1;
}
else
minx=maxx1=sa[i];
}
return 0;
}

总之,理解Height数组以及SA数组的定义,就能够处理很多字符串的问题
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: