#后缀数组
主要思想就是先对单个字符排序,再根据单个字符排序的结果通过移位得到下一个单个字符的排序结果,再排序就可以得到两个字符组成的子串的排序结果;再移位和合并排序得到四个字符组成的子串的排序结果…因此要确定次序的子串长度是按指数增长的,加之使用基数排序每次的复杂度为$O(n)$,总的时间复杂度为$O(nlogn)$.
int wa[maxn],wb[maxn],wv[maxn],ws[maxn];
int cmp(int *r,int a,int b,int l) {
return r[a]==r[b]&&r[a+l]==r[b+l];
}
void da(int *r,int *sa,int n,int m) {
int i,j,p,*x=wa,*y=wb,*t;
for(i=0;i<m;i++) ws[i]=0;
for(i=0;i<n;i++) ws[x[i]=r[i]]++;
for(i=1;i<m;i++) ws[i]+=ws[i-1];
for(i=n-1;i>=0;i--) sa[--ws[x[i]]]=i;
for(j=1,p=1;p<n;j*=2,m=p)
{
for(p=0,i=n-j;i<n;i++) y[p++]=i;
for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;
for(i=0;i<n;i++) wv[i]=x[y[i]];
for(i=0;i<m;i++) ws[i]=0;
for(i=0;i<n;i++) ws[wv[i]]++;
for(i=1;i<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++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
}
return;
}
我也是看着例子模拟了一遍才搞懂的,论文里例子的字符串是$aabaaaab
$,下面是根据这个字符串模拟了一遍算法:
主要思想是将整个字符串分为两个部分,模3为0的后缀(记为A)和模3不为0的后缀(记为B),然后对B的两部分(模3为1和模3为2的部分)进行连接,得到一个新字符串,然后每3个字符看作1个字符进行基数排序,得到一个次序(如果存在完全相同的两个字符,那么递归调用这个函数,直到全部分出次序为止),最后将这个B部分的次序作为第二关键字与A部分排好序后的第一关键字进行合并,就可以得到最终的后缀数组
#define F(x) ((x)/3+((x)%3==1?0:tb))
#define G(x) ((x)<tb?(x)*3+1:((x)-tb)*3+2)
int wa[maxn],wb[maxn],wv[maxn],ws[maxn];
int c0(int *r,int a,int b) {
return r[a]==r[b]&&r[a+1]==r[b+1]&&r[a+2]==r[b+2];
}
int c12(int k,int *r,int a,int b) {
if(k==2)
return r[a]<r[b]||r[a]==r[b]&&c12(1,r,a+1,b+1);
else
return r[a]<r[b]||r[a]==r[b]&&wv[a+1]<wv[b+1];
}
void sort(int *r,int *a,int *b,int n,int m) {
int i;
for(i=0;i<n;i++) wv[i]=r[a[i]];
for(i=0;i<m;i++) ws[i]=0;
for(i=0;i<n;i++) ws[wv[i]]++;
for(i=1;i<m;i++) ws[i]+=ws[i-1];
for(i=n-1;i>=0;i--) b[--ws[wv[i]]]=a[i];
return;
}
void dc3(int *r,int *sa,int n,int m)
{
int i,j,*rn=r+n,*san=sa+n,ta=0,tb=(n+1)/3,tbc=0,p;
r[n]=r[n+1]=0;
for(i=0;i<n;i++) if(i%3!=0) wa[tbc++]=i;
sort(r+2,wa,wb,tbc,m);
sort(r+1,wb,wa,tbc,m);
sort(r,wa,wb,tbc,m);
for(p=1,rn[F(wb[0])]=0,i=1;i<tbc;i++)
rn[F(wb[i])]=c0(r,wb[i-1],wb[i])?p-1:p++;
if(p<tbc) dc3(rn,san,tbc,p);
else for(i=0;i<tbc;i++) san[rn[i]]=i;
for(i=0;i<tbc;i++) if(san[i]<tb) wb[ta++]=san[i]*3;
if(n%3==1) wb[ta++]=n-1;
sort(r,wb,wa,ta,m);
for(i=0;i<tbc;i++) wv[wb[i]=G(san[i])]=i;
for(i=0,j=0,p=0;i<ta && j<tbc;p++)
sa[p]=c12(wb[j]%3,r,wa[i],wb[j])?wa[i++]:wb[j++];
for(;i<ta;p++) sa[p]=wa[i++];
for(;j<tbc;p++) sa[p]=wb[j++];
return;
}
论文里的例子是字符串$aabaaaaba
$,下面也是模拟的一遍过程:
height数组是指在排好序的后缀中,相邻后缀的最长公共前缀。比如$rank[i]$的后缀$sa[rank[1]]$和$rank[i-1]$的后缀$sa[rank[i-1]]$,它们的最长公共前缀记为$height[rank[i]]$,简记为$h[i]$,也就是在字符串中起始位置为$i$的后缀与字典序中它前面一位的后缀的最长公共前缀的长度
对于任意的$i\ge 1$,有$h[i]>h[i-1]-1$
证明:假设$i$和$j$满足$rank[i]-rank[j]=1$,那么这两个后缀的最长公共前缀长度即为$h[i]$,现在考虑原字符串中起始位置在$j$之后和$i$之后的两个后缀,这里记为$j+1$和$i+1$
第一种情况:第$i$和$j$两个后缀的首字符不一样,那么$h[i]=0$,总有$h[i+1]\ge h[i]-1$
第二种情况:第$i$和$j$两个后缀的首字符一样,那么第$i+1$和第$j+1$这两个后缀分别是由第$i$和第$j$个后缀去掉首字符得到的,显然第$j+1$个字符排在第$i+1$个后缀之前,它们的最长公共前缀长度为$h[i]-1$;再考虑比第$i+1$个后缀排序更靠前的后缀中,一定是与它相邻的后缀和它有最长的公共前缀(可以理解为相似度更高),也就是说名次为$rank[i+1]$(这里的$i+1$是指在字符串里的位置)的后缀和名次为$rank[i+1]-1$的后缀的最长公共前缀至少是$h[i]-1$(因为第$j+1$个后缀至少也要在第$i+1$个后缀的前面一位),即$h[i+1]\ge h[i]-1$(这里$i$和$i+1$就是指在字符串里的位置)
这里举个栗子,对于字符串$aabaaaab
$,它的$rank$数组为[4,6,8,1,2,3,5,7],我们假设$i=6,j=5$;那么$suffix(i)=abaaaab
,suffix(j)=ab$,而第$i+1$个后缀为$baaaab$,第$j+1$个后缀为$b$,第$j+1$个后缀一定在$i+1$个后缀的前面,而且第$i+1$个后缀的$rank$值为8,第$j+1$个后缀的$rank$值为7,我们说$height[rank[7]]\ge height[rank[6]]-1$,这里因为第$j+1$个后缀的排名恰为第$i+1$个后缀的排名的前一个,所以取等号
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++);
return;
}