这几天一直在研究后缀数组,说实在,到现在还没有完全研究透。今天终于参考各种资料和代码,仿照别人的写出了一个求后缀数组和最长公共前缀的代码,记下来主要是为了将来复习。
1,有关后缀数组的理论介绍就不介绍了,可以参考罗穗骞和许智磊两位大牛的论文。
2,网上实现很多,但是我没有找到注释比较详细的实现,我这里主要结合代码给出详细的注释和需要注意的细节。
下面结合POJ 1743给出代码和注释:
#include <iostream> #include <cstdlib> #include <cstdio> #include <cstring> using namespace std; const int max_value=20001; int m[max_value];//给定的字符串或者整数序列 /* *sa和lstsa是后缀数组,其中sa用于第k迭代所求后缀数组,lstsa是k-1次迭代所求后缀数组。 */ int sa[max_value],lstsa[max_value]; /* *rank和lstrank名次数组,其中rank用于第k次所求名次数组,lstrank是第k-1次所求名次数组。 注意的细节是,他们应该都初始化为小于任何名次的值,如果名次从1开始,那么应该初始化rank为0或负值。因为会有这样的情况存在aaa,如果初始化rank为0,并且假设名次是从0开始的话,则会出现rank[0]=rank[1]=rank[2]的结果。因为采用的快速排序进行排序,并且迭代过程中的比较lstrank[*x+pow]-lstrank[*y+pow]最多越界到lstrank[n],比如当阶数pow=1是,比较a(suff(2))和aa(suff(1))的大小,首先比较第一个lstrank[1]和lstrank[2],结果是相等的,然后比较lstrank[1+pow]和lstrank[2+pow],这是对于a来说,2+pow越界到lstrank[n](n=3),这时候必须保证lstrank[3]初始化为比任何名次小的值,比如说-1,才能保证aa>a。 可以证明,后续的每次迭代越界比较最多到lstrank[n],这时候通过lstrank的初始值可以比较两个后缀的大小。 */ int rank[max_value],lstrank[max_value]; int h[max_value];//h[i]表示suff(i)和suff(sa[i-1])的LCS int pow;//每次迭代的阶 int cmp1(const void * a,const void *b) { const int * x = (const int *)a; const int * y = (const int *)b; return m[*x]-m[*y]; } int cmp2(const void * a,const void * b) { const int * x= (const int *)a; const int * y = (const int *)b; //首先比较suff[*x]和suff[*y]的前pow部分的大小,如果不能确定 //再比较后pow的大小,如前所说,在比较后pow部分大小的时候lstrank可能越界 //但是,最多越界到n(假设名次从0开始)就可以得出比较结果 //比如,假设pow=2, suff(*x)="aaab",suff(*y)="aaa" //首先比较"aa"和"aa",然后比较"ab"和"a",这里对于"a",lstrank就越界了, //但是,向前看,相当于当pow=1时比较,"ab"和"a"的大小,同样先较"a"和"a",然后比较 //"b"和lstrank[n],由于正确的初始化了lstrank,可以得出"b">lstrank[n] //这也就保证了整个比较的正确性:即当比较两个后缀后半部分,但是他们的后半部分 //长度不相 等时,通过正确的初始化总能得到正确的结果。 if(lstrank[*x]!=lstrank[*y]) return lstrank[*x]-lstrank[*y]; else return lstrank[*x+pow]-lstrank[*y+pow]; } void suffix_array(int n) { int i=0; int j = 0; //caculate sa and rank when pow = 1; for(i=1;i<=n;i++) sa[i]=i; qsort(sa,n,sizeof(int),cmp1); for(i=1,j=0;i<=n;i++) { if(i==1 || m[sa[i]]!=m[sa[i-1]])//when there are some continous-muti,e."aabbbaa",they have the same rank j++; rank[sa[i]]=j; } //caculate sa and rank when pow<<1 using lstrank and lstsa for(pow=1;pow<n;pow<<=1)//pow<<1 { memcpy(lstsa,sa,sizeof(int)*n); memcpy(lstrank,rank,sizeof(int)*n); qsort(sa,n,sizeof(int),cmp2); for(i=1,j=0;i<=n;i++) { if(i==1 || cmp2(&sa[i],&sa[i-1])!=0) j++; rank[sa[i]]=j; } } } //cal the LCS between rank-adjacent suff //h[i] is the LCS between suff(sa[i]) and suff(sa[i-1]) //h[rank[i]] is the LCS between suff(i) and suff(sa[[rank[i]-1]) //h[rank[i]]>=h[rank[i-1]]-1 //that means "the LCS between the suff(i) and suff(sa[rank[i]-1])" is at least "the LCS between the suff(i-1) and suff(sa[rank[i]-1])-1" //so to calculate h[rank[i]], firstly calculate h[rank[i-1]] void cal_height(int n ) { int i=0,j=0,k=0; for(i=1;i<=n;i++) { if(rank[i]==1) { h[rank[i]]=k=0; } else { if(k>0) k--;//sub the first 'char' of suff(i-1) is the suff(i) j = sa[rank[i]-1]; for(;m[i+k]==m[j+k];k++);//start from k, the cmp before k has been done when calculating h[rank[i-1]] h[rank[i]] = k; } } } //计算最长重复不重叠子串 //依次判断长度为1,2,...,n的“重复不重叠子串”,是否存在,可以用二分法显判断n/2... /** * 判断长度为k的“重复不重叠子串”是否存在 */ bool check(int n,int k) { //根据sa(已排序的后缀)滚动,同时判断h[i](名次相邻的两个后缀的LCS) // int i = 0; int low_pos=sa[1]; int high_pos=sa[1]; for(i=2;i<=n;i++) { if(h[i] >=k) { if(sa[i]>high_pos) high_pos = sa[i]; if(sa[i]<low_pos) low_pos = sa[i]; if(high_pos - low_pos > k) return true; } else { low_pos = sa[i]; high_pos = sa[i]; } } return false; } int main() { int n ; scanf("%d",&n); int max_len = 0; while(n) { memset(sa,0,sizeof(sa)); memset(rank,0,sizeof(rank)); memset(lstsa,0,sizeof(lstsa)); memset(lstrank,0,sizeof(lstrank)); memset(h,0,sizeof(h)); memset(m,0,sizeof(m)); max_len = 0; int i = 0; int tmp1; int tmp2; scanf("%d",&tmp1); n--; for(i=1;i<=n;i++) { //根据题目要求,对输入进行转化,使其符合求“最长重复不重叠的字串”条件 scanf("%d",&tmp2); m[i]=tmp2-tmp1+100; tmp1 = tmp2; } suffix_array(n); cal_height(n); int l=0,h=n/2; int mid=0; while(l<=h) { mid = l+(h-l+1)/2; if(check(n,mid)) l=mid+1; else h=mid-1; } if(h>=4) cout<<h+1<<endl; else cout<<0<<endl; cin>>n; } }