西安回来后一个月没写代码了,前段时间太忙,这两天算是好点了,学习了一下后缀数组。
这里有几个关键的数组。
s是原始字符数组,sa是后缀数组,sa[i]表示第i大后缀的开始位置,rank是名次数组,rank[i]表示位置i的数组是第几大(相当于sa[i]反过来),height[i]是后缀sa[i]和后缀sa[i-1]的最长公共前缀,x是当前rank,y是辅助数组。
主要思路就是这个图,每次给后缀的前2^k个字符排序,最多logn次排序,如果用基数排序复杂度为O(n),总复杂度为O(nlogn)。
关于height数组的计算:设h[i]=height[rank[i]],h[i]>=h[i-1]-1(因为后缀i比后缀i-1少最左边一个字符,因此h[i]最小都是h[i-1]-1),这样不必每次从头比较了,复杂度O(n)。
有一点要注意的是,一般在s数组最后加一个0,并且其它元素都非0。这是为了防止数组越界,省去了特判的麻烦(因为如果s为1111,sa={0,1,2,3},k=1,计算x的时候比较最后两个元素第一维的时候相等,此时比较第二维,最后一个位置+1就越界了,因为最后一个位置是没有第二维的),加了个0后,如果第一维相等,也就是y[sa[i-1]]==y[sa[i]],这个时候sa[i-1]+k和sa[i]+k是小于n的,因为如果第一维相等,两个串最后一个字符肯定不会是0,否则就不会相等(因为只有最后一个字符是0),所以加了k也不会越界。
struct SuffixArray{
int s[MAXN]; //原始字符数组
int sa[MAXN]; //后缀数组,sa[i]为第i小后缀在s中的下标,最后一个字符是0,前面非0
int rank[MAXN]; //名次数组,rank[i]为s[i]后缀是第几小,rank[n-1]=0
int height[MAXN]; //height[i]为sa[i-1]和sa[i]的最长公共前缀
int c[MAXN]; //基数排序数组
int t[MAXN],t2[MAXN]; //x,y辅助数组
int n; //字符个数
void clear(){
n=0;
memset(sa,0,sizeof(sa));
}
//m为最大字符值+1,调用前需设置好s和n
void build_sa(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;
for(int k=1;k<=n;k<<=1){
int p=0;
//用sa数组排序第二关键字
for(i=n-k;i<n;i++) y[p++]=i;
for(i=0;i<n;i++) if(sa[i]>=k) y[p++]=sa[i]-k;
//基数排序第一关键字
for(int i=0;i<m;i++) c[i]=0;
for(int i=0;i<n;i++) c[x[y[i]]]++;
for(int i=1;i<m;i++) c[i]+=c[i-1];
for(int i=n-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i];
swap(x,y);
p=1;
x[sa[0]]=0;
for(int i=1;i<n;i++) x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++;
if(p>=n) break;
m=p;
}
}
void build_height(){
int k=0;
for(int i=0;i<n;i++) rank[sa[i]]=i;
height[0]=0;
for(int i=0;i<n-1;i++){
if(k) k--;
int j=sa[rank[i]-1];
while(s[i+k]==s[j+k]) k++;
height[rank[i]]=k;
}
}
};
本文介绍了后缀数组的数据结构实现及核心算法,包括后缀数组sa、名次数组rank、高度数组height的定义与用途。通过逐步解析,展示了如何利用基数排序进行后缀排序的过程,并给出了高度数组的高效计算方法。
3006

被折叠的 条评论
为什么被折叠?



