一些定义:设字符串S的长度为n,S[0~n-1]。
子串:设0<=i<=j<=n-1,那么由S的第i到第j个字符组成的串为它的子串S[i,j]。
后缀:设0<=i<=n-1,那么子串S[i,n-1]称作它的后缀,用Suffix[i]表示。
串比较:对于两个串S1,S2,设长度分别为n1,n2。若存在一个位置i,使得对于0<=j<i满足S1[j]=S2[j]且S1[i]<S2[i],那么我们称S1<S2。如果S1是S2的一个前缀,那么也有S1<S2。两个串相等当且仅当长度相同且所有位置的字母都相同。所以,对于S的任意两个不同的后缀Suffix[i],Suffix[j] ,它们一定是不相等的,因为它们的长度都不同。
后缀数组:设我们用数组sa表示S的后缀数组,0<=sa[i]<=n-1,表示将S的n个后缀从小到大排序后,排名第i的后缀的位置是sa[i]。
名次数组:设我们用数组rank表示S的名次数组,0<=rank[i]<=n-1,表示将S的n个后缀从小到大排序后,后缀Suffix[i]的排名是rank[i]。很明显,sa[rank[i]]=i。
现在我们的问题是,给出一个字符串S,长度为n,S[0~n-1],字符集大小为m。求出S的后缀数组sa。有两种方法计算这个sa,倍增法和DC3法。我们设m<=n(一般情况也是这样子的吧。。)。那么倍增法的时间复杂度是O(nlog(n)),DC3的时间复杂度是O(n)。两个方法的空间复杂度都是O(n)。
倍增法
倍增法的思路是:
(1)首先计算S[0],S[1],...,S[n-1]的排名(注意这个单个字符的排序)。比如,对于aabaaaab,排序后为:1,1,2,1,1,1,1,2
(2)计算子串S[0,1],S[1,2],S[2,3],...,S[n-2,n-1],S[n-1,null] 的排名(注意最后一个的第二个字符为空),由于我们知道了单个字符的排名, 那么每个子串可以用一个二元组来表示,比如S[0,1]={1,1},S[1,2]={1,2},S[2,3]={2,1},等等,也就是aa,ab,ba,aa,aa,aa,ab,b$(我们用$表示空)的排名,排序后为:1,2,4,1,1,1,2,3
(3)计算子串S[0,1,2,3],S[1,2,3,4],S[2,3,4,5],...,S[n-4,n-3,n-2,n-1],S[n-3,n-2,n-1,$],S[n-2,n-1,$,$],S[n-1,$,$,$]。方法与上面相同。依次类推,每次使用两个2^(x-1)长度的子串来计算2^x次方长度的子串的排名,直到某一次排序后n个数字各不相同。最后,对于串aabaaaab,如下所示
在实现的时候,一般在串的最后加一个空字符。这个字符比其他任何字符都小。下面是一份实现的程序
1 class SuffixArray 2 { 3 private: 4 static const int N=100005; /**字符串最大长度**/ 5 int wa[N],wb[N],wd[N],r[N]; 6 7 bool isSame(int *r,int a,int b,int len) 8 { 9 return r[a]==r[b]&&r[a+len]==r[b+len]; 10 } 11 void da(int *r,int *sa,int n,int m) 12 { 13 int *x=wa,*y=wb,*t; 14 for(int i=0;i<m;i++) wd[i]=0; 15 for(int i=0;i<n;i++) wd[x[i]=r[i]]++; 16 for(int i=1;i<m;i++) wd[i]+=wd[i-1]; 17 for(int i=n-1;i>=0;i--) sa[--wd[x[i]]]=i; 18 /**基数排序计算长度为1的子串的排名 19 相同的 越靠前 排名越小**/ 20 for(int j=1,p=1;p<n;j<<=1,m=p) 21 { 22 p=0; 23 for(int i=n-j;i<=n-1;i++) y[p++]=i; 24 for(int i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j; 25 /**y[i]表示对于组成2^j的所有子串的二元组 26 {pi,qi}来说,第二关键字即qi排名为i的位置为y[i] **/ 27 28 29 for(int i=0;i<m;i++) wd[i]=0; 30 for(int i=0;i<n;i++) wd[x[i]]++; 31 for(int i=1;i<m;i++) wd[i]+=wd[i-1]; 32 for(int i=n-1;i>=0;i--) sa[--wd[x[y[i]]]]=y[i]; 33 /**这里倒着枚举 当两个位置的y[i]和y[j]对应的x 34 相同时,后面的排名大,因为它的第二关键字 35 即y的排名大 而x在外面也决定了排名以第一 36 关键字为主 **/ 37 t=x;x=y;y=t;p=1; 38 x[sa[0]]=0; 39 for(int i=1;i<n;i++) x[sa[i]]=isSame(y,sa[i-1],sa[i],j)?p-1:p++; 40 } 41 } 42 public: 43 /**字符串S,长度n,S[0,n-1],为方便,我们假设它只包含小写字母 44 最后的后缀数组存储在sa[1~n]中 0<=sa[i]<=n-1 45 **/ 46 void calSuffixArray(char *S,int n,int *sa) 47 { 48 for(int i=0;i<n;i++) r[i]=S[i]-'a'+1; 49 r[n++]=0; 50 da(r,sa,n,27); 51 } 52 };