阅兵假期搞了后缀数组(倍增法),今天准备去Get DC3算法,但是回过头来看倍增法,竟然发现不少东西耶。扎扎决定要写一个总结帖。
感觉其实倍增法中只要搞明白了每个数组都代表的什么意思,理解起来并不是很难,只是在排序的时候引入了一种崭新并且稳定的排序方法(一定要给想出这个算法的聚聚们狠狠的点个赞真是把数组用到了极致)。
先介绍基数排序好辣,比如说有一串数字:73, 22, 93, 43, 55, 14, 28, 65, 39, 81
第一步先对个位(第一关键字)排序,可得结果是:81, 22, 73, 93, 43, 14, 55, 65, 28, 39
然后再按照十位(第二关键字)排序,最终得出:14, 22, 28, 39, 43, 55, 65, 73, 81, 93
然后就是倍增法:先对每一个字符进行排序,然后再把排好序的子串再作为一个整体,然后相连的两个整体作为一个对象进行排序,只到最后文本串变为一个整体即可。
重要的就是如何倍增排出顺序,我们在这之前先介绍一下算法中数组代表的含义:
suffix[i] 以i为起始位置的后缀串
rank[i] 以第i为字母开头的后缀串的排名
sa[i] 排名为i后缀串的起始位置
height[i] suffix[sa[i-1]]与suffix[sa[i]]的公共前缀的长度
1 //后缀数组(倍增法) 2 3 int sa[maxn], rank[maxn], height[maxn]; 4 int t1[maxn], t2[maxn], r[maxn], c[maxn]; 5 bool cmp (int *str, int a, int b, int k) 6 { 7 //rank相邻的两个串,第一第二关键字都一样,rank一样 8 return str[a]==str[b] && str[a+k]==str[b+k]; 9 } 10 int da (int *str, int n, int m) 11 { 12 //文本串,文本串长度,文本串中最大的字符+1 13 int *x = t1, *y = t2, i, j; 14 n ++; 15 //首次基数排序,对每位数字进行排序 16 for (i=0; i<m; i++) c[i] = 0; 17 for (i=0; i<n; i++) c[x[i] = str[i]] ++; 18 for (i=1; i<m; i++) c[i] += c[i-1]; 19 for (i=n-1; i>=0; i--) sa[-- c[str[i]]] = i; 20 for (j=1; j<=n; j*=2) 21 { 22 //用sa数组对第二关键字排序 23 int p = 0; 24 for (i=n-j; i<n; i++) y[p++] = i; 25 //[n-j,n)没有对应的第二关键字,故第二关键字排名最小 26 for (i=0; i<n; i++) if (sa[i] >= j) y[p++] = sa[i] - j; 27 //i为关键字排名,第二关键字起始位置在[1,j),不会有第一关键字 28 //更新sa数组,y[i] 排名为i的第二关键字所对应第一关键字的起始位置 29 for (i=0; i<m; i++) c[i] = 0; 30 for (i=0; i<n; i++) c[x[y[i]]] ++; 31 for (i=1; i<m; i++) c[i] += c[i-1]; 32 for (i=n-1; i>=0; i--) sa[-- c[x[y[i]]]] = y[i]; 33 //更新x数组,x[i] 起始位置为i的关键字1的排名 34 swap (x, y); 35 p = 1, x[sa[0]] = 0; 36 for (i=1; i<n; i++) 37 x[sa[i]] = cmp(y, sa[i-1], sa[i], j)?p-1:p++; 38 if (p >= n) 39 break; 40 m = p; 41 } 42 //计算rank数组 43 for (i=0; i<n; i++) 44 rank[sa[i]] = i; 45 //计算height数组 46 n --; 47 int k = 0; 48 for (i=0; i<n; i++) 49 { 50 //枚举起点, height[名次] 51 if (k) k --; 52 j = sa[rank[i]-1]; 53 while (str[i+k]==str[j+k]) k++; 54 height[rank[i]] = k; 55 } 56 }