参考
2009国家集训队论文 后缀数组——处理字符串的有力工具 ——罗穗骞
模板
bool cmp(int* r, int a, int b, int l) { return r[a] == r[b] && r[a+l] == r[b+l]; }
void init(int* r, int* sa, int n, int m) {
int* x=wa, *y=wb, *t, i, j, p;
for (i = 0; i < m; ++i) wt[i] = 0;
for (i = 0; i < n; ++i) ++wt[x[i] = r[i]];
for (i = 1; i < m; ++i) wt[i] += wt[i - 1];
for (i = n-1; i >= 0; --i) sa[--wt[x[i]]] = i;
for (j = 1, p = 1; p < n; j <<= 1, m = p) {
//注意!每次循环要记得更新m的值!
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) wt[i] = 0;
for (i = 0; i < n; ++i) ++wt[wv[i]];
for (i = 1; i < m; ++i) wt[i] += wt[i - 1];
for (i = n-1; i >= 0; --i) sa[--wt[wv[i]]] = y[i];
t = x, x = y, y = t, x[sa[0]] = 0;
for (p = 1, i = 1; i < n; ++i) x[sa[i]] = cmp(y, sa[i], sa[i-1], j) ? p - 1 : p++;
}
for (i = 0; i < n; ++i) rk[sa[i]] = i;
int k = 0;
for (i = 0; i < n - 1; h[rk[i++]] = k) {
//注意!这里上界是n-1而不是n,因为sa[n-1] = 0
for (k = k ? --k : 0, j = sa[rk[i] - 1]; r[i+k] == r[j+k]; ++k);
}
}
解释
请在理解了算法的基础上食用
参数
void init(int* r, int* sa, int n, int m);
r :原字符串
m :原字符串中最大的字符值
第一部分
for (i = 0; i < m; ++i) wt[i] = 0;
for (i = 0; i < n; ++i) ++wt[x[i] = r[i]];
for (i = 1; i < m; ++i) wt[i] += wt[i - 1];
for (i = n-1; i >= 0; --i) sa[--wt[x[i]]] = i;
x[ ]
数组和后面的用法统一,
x[i]
记录以
i
开头,长度为
sa[ ]
数组含义见上,
sa[i]
记录排名第
i
名字符串的开头位置,在计算过程中,排名是针对
x[ ] 与 sa[ ] 互为逆运算。
第二部分
循环体
for (j = 1, p = 1; p < n; j <<= 1, m = p) {
//注意!每次循环要记得更新m的值!
当某一次循环结束后所有排名都不相同即可停止,即名次数等于 n .
计数排序的上限
循环体内
- 1.
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;
我们知道,倍增算法的主要思想是基数排序,在这里,基数排序有两个关键字。
y[ ] 数组记录的是 以第二关键字排序后 第一关键字 的下标位置
即
y[i]=k
的含义是:排名第
i
的第二关键字,其下标为
具体操作上:
显然对于字符串的最后
j
位,它们的第二关键字均为
再从头到尾遍历一遍,将
-
2.
for (i = 0; i < n; ++i) wv[i] = x[y[i]];
x[i]
记录以
i
开头,长度为
由此,我们得出, wv[i] 记录 排名第 i 的第二关键字,其对应的第一关键字的排名。也即第二关键字升序排序后对应的第一关键字。
3.
for (i = 0; i < m; ++i) wt[i] = 0;
for (i = 0; i < n; ++i) ++wt[wv[i]];
for (i = 1; i < m; ++i) wt[i] += wt[i - 1];
for (i = n-1; i >= 0; --i) sa[--wt[wv[i]]] = y[i];
再做一遍计数排序,使得在第二关键字升序的基础上,第一关键字也升序,将下标一个个填进去。
4.
t = x, x = y, y = t, x[sa[0]] = 0;
for (p = 1, i = 1; i < n; ++i, ++p) x[sa[i]] = cmp(y, sa[i], sa[i-1], j) ? p - 1 : p;
因为
因为可能有相同的字符串,所以并不是单纯的逆运算,还需要用 cmp() 处理一下排名相同的情况。
bool cmp(int* r, int a, int b, int l) { return r[a] == r[b] && r[a+l] == r[b+l]; }
只需比较第一关键字和第二关键字即可。(注意,这里的
r
不是初始字符串,传进去的参数事实上是
第三部分
for (i = 0; i < n; ++i) rk[sa[i]] = i;
int k = 0;
for (i = 0; i < n - 1; h[rk[i++]] = k) {
//注意!这里上界是n-1而不是n,因为sa[n-1] = 0
for (k = k ? --k : 0, j = sa[rk[i] - 1]; r[i+k] == r[j+k]; ++k);
}
求