参考博客:
https://www.cnblogs.com/jinkun113/p/4743694.html
https://www.cnblogs.com/victorique/p/8480093.html
https://www.cnblogs.com/jackdong/archive/2012/10/15/2724034.html
https://www.cnblogs.com/shanchuan04/p/5324009.html
https://www.cnblogs.com/zwfymqz/p/8413523.html
后缀树组的精髓在于倍增算法、基数排序、和一些神奇的优化,巧妙的利用各个后缀之间的关系。理解了很长时间。
基数排序在后缀数组中可以在O(n)的时间内对一个二元组(p,q)进行排序,其中p是第一关键字,q是第二关键字,比其他的排序算法都要优越。
附上洛谷3809模板代码:
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef unsigned int ui; 4 typedef long long ll; 5 typedef unsigned long long ull; 6 #define pf printf 7 #define mem(a,b) memset(a,b,sizeof(a)) 8 #define prime1 1e9+7 9 #define prime2 1e9+9 10 #define pi 3.14159265 11 #define lson l,mid,rt<<1 12 #define rson mid+1,r,rt<<1|1 13 #define scand(x) scanf("%llf",&x) 14 #define f(i,a,b) for(int i=a;i<=b;i++) 15 #define scan(a) scanf("%d",&a) 16 #define dbg(args) cout<<#args<<":"<<args<<endl; 17 #define inf 0x3f3f3f3f 18 const int maxn=1e6+5; 19 int n,m,t; 20 using namespace std; 21 char s[maxn]; 22 int y[maxn],x[maxn],c[maxn],sa[maxn],rk[maxn],height[maxn],wt[30]; 23 void getsa() 24 { 25 for (int i=1;i<=n;++i) ++c[x[i]=s[i]]; 26 //c数组是桶 27 //x[i]是第i个元素的第一关键字 28 for (int i=2;i<=m;++i) c[i]+=c[i-1]; 29 //做c的前缀和,我们就可以得出每个关键字最多是在第几名 30 for (int i=n;i>=1;--i) sa[c[x[i]]--]=i; 31 for (int k=1;k<=n;k<<=1) 32 { 33 int num=0; 34 for (int i=n-k+1;i<=n;++i) y[++num]=i; 35 //y[i]表示第二关键字排名为i的数,第一关键字的位置 36 //第n-k+1到第n位是没有第二关键字的 所以排名在最前面 37 for (int i=1;i<=n;++i) if (sa[i]>k) y[++num]=sa[i]-k; 38 //排名为i的数 在数组中是否在第k位以后 39 //如果满足(sa[i]>k) 那么它可以作为别人的第二关键字,就把它的第一关键字的位置添加进y就行了 40 //所以i枚举的是第二关键字的排名,第二关键字靠前的先入队 41 for (int i=1;i<=m;++i) c[i]=0; 42 //初始化c桶 43 for (int i=1;i<=n;++i) ++c[x[i]]; 44 //因为上一次循环已经算出了这次的第一关键字 所以直接加就行了 45 for (int i=2;i<=m;++i) c[i]+=c[i-1];//第一关键字排名为1~i的数有多少个 46 for (int i=n;i>=1;--i) sa[c[x[y[i]]]--]=y[i],y[i]=0; 47 //因为y的顺序是按照第二关键字的顺序来排的 48 //第二关键字靠后的,在同一个第一关键字桶中排名越靠后 49 //基数排序 50 swap(x,y); 51 //这里不用想太多,因为要生成新的x时要用到旧的,就把旧的复制下来,没别的意思 52 x[sa[1]]=1;num=1; 53 for (int i=2;i<=n;++i) 54 x[sa[i]]=(y[sa[i]]==y[sa[i-1]] && y[sa[i]+k]==y[sa[i-1]+k]) ? num : ++num; 55 //因为sa[i]已经排好序了,所以可以按排名枚举,生成下一次的第一关键字 56 if (num==n) break; 57 m=num; 58 //这里就不用那个122了,因为都有新的编号了 59 } 60 } 61 void geth() 62 { 63 int k=0; 64 for (int i=1;i<=n;++i) rk[sa[i]]=i; 65 for (int i=1;i<=n;++i) 66 { 67 if (rk[i]==1) continue;//第一名height为0 68 if (k) --k;//h[i]>=h[i-1]+1; 69 int j=sa[rk[i]-1]; 70 while (j+k<=n && i+k<=n && s[i+k]==s[j+k]) ++k; 71 height[rk[i]]=k;//h[i]=height[rk[i]]; 72 } 73 } 74 int main() 75 { 76 gets(s+1); 77 n=strlen(s+1);m=122; 78 //n表示原字符串长度,m表示字符个数,ascll('z')=122 79 //我们第一次读入字符直接不用转化,按原来的ascll码来就可以了 80 //因为转化数字和大小写字母还得分类讨论,怪麻烦的 81 getsa(); 82 geth(); 83 for(int i=1;i<=n;i++)cout<<sa[i]<<" "; 84 }
另一份大佬详解:
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 5 using namespace std; 6 7 const int N = 1000100; 8 9 char s[N]; 10 int sa[N],t1[N],t2[N],c[N],rnk[N],height[N]; 11 //sa[i] 排名为i的是谁 -下标 12 //rnk[i] i的排名 -名次 13 //height[i] 排名为i的后缀与排名为i-1的后缀的最长公共前缀长度 14 int n,m = 130; 15 16 void get_sa() { 17 int *x = t1,*y = t2; 18 //基数排序 19 for (int i=0; i<m; ++i) c[i] = 0; 20 for (int i=0; i<n; ++i) c[x[i] = s[i]]++; 21 for (int i=1; i<m; ++i) c[i] += c[i-1]; 22 for (int i=n-1; i>=0; --i) sa[--c[x[i]]] = i; 23 /* 24 每次循环 25 都将两个长度k子串合并为一个长度为2k的串 26 其中前k个字符构成的子串的排名为第一关键字,后k个字符为第二关键字 27 并求出合并后的字符串的排名 28 */ 29 //每次排名得到一共有多少个排名p,由p优化m的值。 30 for (int k=1; k<=n; k<<=1) { 31 int p = 0; 32 /* 33 在上一轮中,第一关键字已经排好了, 此时只需要排第二关键字即可。 34 y数组是第二关键字排序的结果,存储的是2k长度的字符串的第一关键字的下标 35 n-k到n-1中所有的元素第二关键字为0 36 */ 37 for (int i=n-k; i<n; ++i) y[p++] = i; 38 for (int i=0; i<n; ++i) if (sa[i] >= k) y[p++] = sa[i] - k; 39 for (int i=0; i<m; ++i) c[i] = 0; 40 for (int i=0; i<n; ++i) c[x[y[i]]]++; 41 for (int i=1; i<m; ++i) c[i] += c[i-1]; 42 for (int i=n-1; i>=0; --i) sa[--c[x[y[i]]]] = y[i]; 43 swap(x,y); 44 p = 1; 45 x[sa[0]] = 0; 46 for (int i=1; i<n; ++i) 47 x[sa[i]] = (y[sa[i-1]]==y[sa[i]] && sa[i-1]+k<n && sa[i]+k<n && 48 y[sa[i-1]+k]==y[sa[i]+k]) ? p-1 : p++; 49 if (p >= n) break;//退出条件:一共有n种排名,说明有序 50 m = p; 51 } 52 } 53 void get_height() { 54 for (int i=0; i<n; ++i) rnk[sa[i]] = i; // sa排名为i的下标,rnk[i]下标为i的后缀的名次。 55 int k = 0; 56 height[0] = 0; 57 /* 58 性质:height[rnk[i]] >= height[rnk[i-1]]-1 59 理解为起始下标为i的后缀和i-1的后缀除了开头第一个字符不同,其余的相同的 60 即suffix[i,n]与suffix[i-1,n]只有第一个字符不同。 61 所以suffix[i-1,n]匹配上的子串,与suffix[i,n]匹配的子串,中k-1个是相等的。 62 */ 63 for (int i=0; i<n; ++i) { 64 if (!rnk[i]) continue; 65 if (k) k--; 66 int j = sa[rnk[i]-1]; // 前一个排名的后缀的开始位置 67 while (i+k<n && j+k<n && s[i+k]==s[j+k]) k++; 68 height[rnk[i]] = k; 69 } 70 } 71 int main() { 72 scanf("%s",s); 73 n = strlen(s); 74 get_sa(); 75 for (int i=0; i<n; ++i) 76 printf("%d ",sa[i]+1); 77 return 0; 78 }