后缀排序
std::vector<int> suffixSort(std::string s);
传入一个字符串 (s),返回数组 (p),(p[i]) 表示按字典序从小到大排名为 (i) 的后缀位置。
倍增法
假设当前 (p) 数组表示字符串集 ({s[i dots i + 2^k - 1]}) 的排名信息(为处理和理解方便,这里将 (s) 当作循环串处理,这样每个字符串的长度都相等,但这样最后得到的是循环后缀的排名信息,可以通过在最后加一个字符集外的字符(例如 $
)来解决这个问题)。
为了得到 ({s[i dots i + 2^{k + 1} - 1]}) 的排名信息(新的 (p) 数组),我们实际上只需要对二元组 ((c_i, c_{i + 2^k})) 排序,这里 (c_i) 表示 (s[i dots i + 2^k - 1]) 的排名,且并列的排名相同。直接使用 std::sort
可以得到代码简单、复杂度 (O(nlog^2n)) 的优秀算法。
但实际上由于 (c_i) 的值不会超过 (n),可以使用基数排序将复杂度优化为 (O(nlog n))。
双关键字的基数排序((pn) 数组是 (c_{i+2^k}))的排名信息):
for (int i = 0; i < n; i++) cnt[c[i]]++;
for (int i = 1; i < m; i++) cnt[i] += cnt[i - 1];
for (int i = n - 1; i >= 0; i--) p[--cnt[c[pn[i]]]] = pn[i];
完整代码:
std::vector<int> suffixSort(std::string s) {
int n = s.length();
std::vector<int> cnt(std::max(n, 256), 0), c(n), cn(n), p(n), pn(n);
for (int i = 0; i < n; i++) cnt[s[i]]++;
for (int i = 1; i < 256; i++) cnt[i] += cnt[i - 1];
for (int i = 0; i < n; i++) p[--cnt[s[i]]] = i;
int m = 1;
c[p[0]] = 0;
for (int i = 1; i < n; i++) {
c[p[i]] = s[p[i]] == s[p[i - 1]] ? m - 1 : m++;
}
for (int k = 1; k < n; k <<= 1) {
for (int i = 0; i < n; i++) {
pn[i] = p[i] >= k ? p[i] - k : p[i] - k + n;
}
memset(&cnt[0], 0, m << 2);
for (int i = 0; i < n; i++) cnt[c[i]]++;
for (int i = 1; i < m; i++) cnt[i] += cnt[i - 1];
for (int i = n - 1; i >= 0; i--) p[--cnt[c[pn[i]]]] = pn[i];
m = 1;
cn[p[0]] = 0;
for (int i = 1, x, y = c[(p[0] + k) % n]; i < n; i++) {
x = y, y = p[i] + k;
y = c[y >= n ? y - n : y];
cn[p[i]] = x == y && c[p[i]] == c[p[i - 1]] ? m - 1 : m++;
}
if (m >= n) break;
c.swap(cn);
}
return p;
}
LCP(最长公共前缀)
int lcp(int i, int j);
求后缀 (i) 和后缀 (j) 的最长公共前缀。
不妨设 (rank_i < rank_j),那么实际上 (operatorname{lcp}(i, j) = min_{k = rank_i }^{rank_j - 1}operatorname{lcp}(p[k], p[k + 1]))。
设 (h[i] = operatorname{lcp}(p[i], p[i + 1])),我们只需要处理出 (h) 数组即可将该问题转化为RMQ问题。
为理解方便,这里的 后缀
仍表示上述后缀排序中的 循环后缀
,可以通过字符串末尾加很小的字符来得到正确的答案。
对于相邻的两个循环后缀 (i - 1, i),很容易发现 (h[rank_{i}] ge h[rank_{i - 1}] - 1),所以计算 (h[rank_i]) 时从 (h[rank_{i - 1}] - 1) 暴力拓展,总拓展次数不会超过 (2n) 次,于是可以用 (O(n)) 时间构造出 (h) 数组。
std::vector<int> getHeight(const std::vector<int> &p) {
int n = p.size();
std::vector<int> rank(n, 0);
for (int i = 0; i < n; i++) rank[p[i]] = i;
int k = 0;
std::vector<int> h(n - 1, 0);
for (int i = 0; i < n; i++) {
if (rank[i] == n - 1) {
k = 0; continue;
}
int j = p[rank[i] + 1];
while (i + k < n && j + k < n && s[i + k] == s[j + k])
k++;
h[rank[i]] = k;
if (k) k--;
}
return h;
}