后缀数组 (Suffix Array)
模板详解
1. 简介
后缀数组是一种重要的字符串数据结构,它将字符串的所有后缀按字典序排序后得到的数组。配合最长公共前缀(LCP)数组,可以解决许多字符串问题。
主要特点:
- O(nlogn) 的构建时间
- 可以处理字符串的所有后缀
- 支持高效的字符串匹配
- 可以解决最长公共子串等问题
2. 实现原理
2.1 基本概念
后缀数组 (SA):
名次数组 (Rank):
LCP数组:
2.2 核心策略
倍增算法:
基数排序:
3. 模板代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| struct SuffixArray { int n; std::vector<int> sa, rk, lc; SuffixArray(const std::string &s) { n = s.length(); sa.resize(n); lc.resize(n - 1); rk.resize(n); std::iota(sa.begin(), sa.end(), 0); std::sort(sa.begin(), sa.end(), [&](int a, int b) { return s[a] < s[b]; }); rk[sa[0]] = 0; for (int i = 1; i < n; ++i) { rk[sa[i]] = rk[sa[i - 1]] + (s[sa[i]] != s[sa[i - 1]]); } int k = 1; std::vector<int> tmp, cnt(n); tmp.reserve(n); while (rk[sa[n - 1]] < n - 1) { tmp.clear(); for (int i = 0; i < k; ++i) { tmp.push_back(n - k + i); } for (auto i : sa) { if (i >= k) { tmp.push_back(i - k); } } std::fill(cnt.begin(), cnt.end(), 0); for (int i = 0; i < n; ++i) { ++cnt[rk[i]]; } for (int i = 1; i < n; ++i) { cnt[i] += cnt[i - 1]; } for (int i = n - 1; i >= 0; --i) { sa[--cnt[rk[tmp[i]]]] = tmp[i]; } std::swap(rk, tmp); rk[sa[0]] = 0; for (int i = 1; i < n; ++i) { rk[sa[i]] = rk[sa[i - 1]] + (tmp[sa[i - 1]] < tmp[sa[i]] || sa[i - 1] + k == n || tmp[sa[i - 1] + k] < tmp[sa[i] + k]); } k *= 2; } for (int i = 0, j = 0; i < n; ++i) { if (rk[i] == 0) { j = 0; } else { j -= j > 0; while (i + j < n && sa[rk[i] - 1] + j < n && s[i + j] == s[sa[rk[i] - 1] + j]) { j++; } lc[rk[i] - 1] = j; } } } };
|
4. 函数说明
4.1 成员变量
1 2 3 4
| int n; std::vector<int> sa; std::vector<int> rk; std::vector<int> lc;
|
4.2 构造函数
- 输入:字符串 s
- 构建后缀数组和 LCP 数组
- 使用倍增算法实现
5. 时间复杂度分析
- 构建后缀数组:O(nlogn)
- 构建 LCP 数组:O(n)
- 空间复杂度:O(n)
6. 应用场景
- 字符串匹配
- 最长公共子串
- 重复子串查找
- 循环移位最小表示
7. 使用示例
7.1 构建后缀数组
1 2 3 4
| string s = "banana"; SuffixArray sa(s);
|
7.2 查找最长重复子串
1 2 3 4 5 6 7 8
| int findLongestRepeatedSubstring(const string& s) { SuffixArray sa(s); int maxLen = 0; for (int i = 0; i < s.length() - 1; i++) { maxLen = max(maxLen, sa.lc[i]); } return maxLen; }
|
8. 注意事项
字符串处理
内存管理
边界情况
9. 总结
后缀数组是一个强大的字符串处理工具,通过高效的构建算法和 LCP
数组的配合,可以解决多种字符串问题。虽然实现较为复杂,但其应用广泛且实用性强,是字符串处理中的重要数据结构。