后缀数组模板
将罗穗骞论文《后缀数组--处理字符串的有力工具》中的实现修改后的版本。
1 void SA(int *s, int *sa, int *sa2, int *rk, int *cnt, int *hgt, int n, int m){ 2 3 //counting sort 4 for(int i=0; i<m; i++) cnt[i]=0; 5 for(int i=0; i<n; i++) cnt[rk[i]=s[i]]++; 6 for(int i=1; i<m; i++) cnt[i]+=cnt[i-1]; 7 for(int i=n-1; i>=0; i--) sa[--cnt[rk[i]]]=i; //stable sort 8 9 for(int len=1; len<n; len*=2){ 10 //stp.1 fill sa2[] 11 int p=0; 12 for(int i=n-len; i<n; i++) sa2[p++]=i; 13 for(int i=0; i<n; i++) if(sa[i]>=len) sa2[p++]=sa[i]-len; 14 //stp.2 fill sa[], countig sort 15 for(int i=0; i<m; i++) cnt[i]=0; 16 for(int i=0; i<n; i++) cnt[rk[i]]++; 17 for(int i=1; i<m; i++) cnt[i]+=cnt[i-1]; 18 for(int i=n-1; i>=0; i--) 19 sa[--cnt[rk[sa2[i]]]]=sa2[i]; 20 //stp.1 and stp.2 together is radix sort 21 22 //fill rk[] 23 swap(rk, sa2); 24 rk[sa[0]]=0; 25 for(int i=1; i<n; i++) 26 rk[sa[i]]=rk[sa[i-1]]+!same(sa2, sa[i-1], sa[i], len); 27 28 m=rk[sa[n-1]]+1; 29 if(m==n) break; 30 } 31 32 //CALCULATE hgt[] 33 34 for(int i=0, j, lcp=0; i<n-1; i++){ 35 lcp?--lcp:0; 36 // rk[i]>0 37 j=sa[rk[i]-1]; 38 for(; s[j+lcp]==s[i+lcp]; lcp++); 39 hgt[rk[i]]=lcp; 40 } 41 }
注意:
1.s[ ]数组的末尾必须补一个“0”,这里“0”的含义是:比s[ ]中其他元素都小的一个值。因而函数
void SA(int *s, int *sa, int *sa2, int *rk, int *cnt, int *hgt, int n, int m)
的参数 n 是 s[ ] 的实际长度再加 1 .
2.由于s[ ]的末尾补了零,必然有:
sa[0]=n (0-indexed)
rk[n]=0
rk[n]=0,意味着rk[0...n-1] > 0
所以求hight数组时,循环才可写成:
for(int i=0, j, lcp=0; i<n-1; i++){ lcp?--lcp:0; // rk[i]>0 j=sa[rk[i]-1]; for(; s[j+lcp]==s[i+lcp]; lcp++); hgt[rk[i]]=lcp; }
因为s[rk[i]-1]不会越界。补"0"以后,hgt[1...n]都变得 well-defined 了。
3. 在函数SA()中,参数rk和sa作为两个指针使用,由于有
swap(rk, sa2);
这一句,在 SA() 外部,我们不知道 sa2[] 和 rk[] 这两个数组里装的分别是什么,这时如果要用 rk[] 数组必须从sa[] 数组重新构造一遍:
for(int i=0; i<=n; i++) rk[sa[i]]=i;
注意:
- 这里的 n 指的是数组的实际长度, 而非末尾补 "0" 后的长度 (后者是作为SA()的参数的 n), 下同
- 因此, for-head 里的判断 i<=n 不能写成 i<n ,因为 sa[ ] 数组是按照补 “0” 后的 s[ ] 数组算得的,必然有 sa[0]=n , 若写成 < 就漏掉 rk[sa[n]]了.