Trie 树
💡 核心思想
Trie(前缀树/字典树)是一种树形数据结构,每条边代表一个字符,从根到某节点的路径上的字符组成一个字符串。支持 $O(|s|)$ 的插入、查询和前缀匹配。变体包括 01-Trie(处理异或问题)。
Trie 的核心思想是"共享前缀"。如果你插入 "abc" 和 "abd",它们共享 "ab" 这条路径,只在最后分叉。这使得前缀查询非常高效。01-Trie 是 Trie 的二进制版本,每个节点只有 0/1 两个儿子,常用于求最大异或和。
🎯 直觉理解
Trie 就像一本字典的目录结构。所有以 "ab" 开头的单词共享 "a→b" 这个前缀路径,然后在 "b" 后面根据下一个字母分叉。查找某个前缀是否出现过,只需要沿路径走一遍就行。
📝 算法流程
- 插入:沿字符路径走,不存在则创建新节点
- 查询:沿路径走,走不到则不存在
- 每个节点维护 son[26] 和 end 标记
- 01-Trie:son[2],处理二进制位
$$\text{Trie 空间:} O(\text{总字符数} \times |\Sigma|)$$
📊 复杂度分析
| 指标 | 复杂度 |
|---|---|
| 时间 | $O(|s|)$ 每次操作 |
| 空间 | $O(N \cdot |\Sigma|)$($N$ 为总字符数) |
💻 参考实现(C++)
C++ (C++17)
#include <bits/stdc++.h>
using namespace std;
int trie[1000005][26], tot = 1;
bool End[1000005];
void insert(const string& s) {
int p = 1;
for (char c : s) {
int ch = c - 'a';
if (!trie[p][ch]) trie[p][ch] = ++tot;
p = trie[p][ch];
}
End[p] = true;
}
bool search(const string& s) {
int p = 1;
for (char c : s) {
int ch = c - 'a';
if (!trie[p][ch]) return false;
p = trie[p][ch];
}
return End[p];
}
int main() {
int n; cin >> n;
while (n--) {
string op, s; cin >> op >> s;
if (op == "insert") insert(s);
else cout << (search(s) ? "Yes" : "No") << "\n";
}
return 0;
}⚠️ 常见坑点
数组大小要开够:节点数 = 所有字符串长度之和
字符集大小不同时 son 数组大小要调整
01-Trie 要从高位到低位处理
删除操作需要维护引用计数
📚 相关题目
| 题目 | 来源 | 难度 | 备注 |
|---|---|---|---|
| P8306 字典树 | 洛谷 | CSP-S | Trie模板 |
| P4551 最长异或路径 | 洛谷 | CSP-S | 01-Trie |