字典树(Trie)是处理字符串匹配的高效数据结构,广泛应用于搜索提示、拼写检查等场景。本文将带你从零掌握字典树的原理与实现!
一、什么是字典树?
字典树(Trie)是一种树形数据结构,用于高效存储和检索字符串集合。它的核心优势在于:
-  前缀共享:具有相同前缀的字符串共享树中的路径 
-  查找高效:查找时间复杂度仅与字符串长度相关(O(L)) 
-  自动排序:存储的字符串按字典序自动排列 
典型应用场景:
-  搜索引擎的自动补全功能 
-  拼写检查与单词提示 
-  IP路由表的最长前缀匹配 
-  单词游戏(如Boggle、Scrabble) 
二、字典树的核心原理
基本结构
字典树是一棵多叉树,每个节点包含:
-  指向子节点的指针数组(通常26个,对应26个字母) 
-  结束标记(标识从根到当前节点是否构成完整单词) 
工作方式
假设我们存储单词:["cat", "car", "dog"]

结构说明:
根节点 (root)
-  不存储具体字符 
-  包含指向所有首字母子节点的指针 
字符节点
-  每个节点存储一个字符 
-  包含 26 个子指针(对应 a-z) 
-  示例路径: -  root → c → a → t形成 "cat"
-  root → c → a → r形成 "car"
-  root → d → o → g形成 "dog"
 
-  
单词结束标记 (*)
-  红色节点表示单词结束位置 
-  t*标记 "cat" 结束
-  r*标记 "car" 结束
-  g*标记 "dog" 结束
共享前缀机制
-  "cat" 和 "car" 共享 c→a路径
-  节省存储空间的关键特性 
三、C++实现字典树
节点结构设计
const int ALPHABET_SIZE = 26;class TrieNode {
public:TrieNode* children[ALPHABET_SIZE];bool isEndOfWord; // 标记是否单词结束TrieNode() {isEndOfWord = false;for (int i = 0; i < ALPHABET_SIZE; i++) {children[i] = nullptr;}}
};
字典树类框架
class Trie {
private:TrieNode* root;// 递归删除节点(用于析构)void deleteTrie(TrieNode* node) {if (!node) return;for (int i = 0; i < ALPHABET_SIZE; i++) {if (node->children[i]) {deleteTrie(node->children[i]);}}delete node;}public:Trie() {root = new TrieNode();}~Trie() {deleteTrie(root);}// 插入单词void insert(string word);// 搜索单词(完全匹配)bool search(string word);// 检查前缀是否存在bool startsWith(string prefix);
};
关键操作实现
1. 插入操作
void Trie::insert(string word) {TrieNode* current = root;for (char c : word) {int index = c - 'a';// 如果字符节点不存在则创建if (!current->children[index]) {current->children[index] = new TrieNode();}current = current->children[index];}current->isEndOfWord = true; // 标记单词结束
}
2. 搜索操作
bool Trie::search(string word) {TrieNode* current = root;for (char c : word) {int index = c - 'a';if (!current->children[index]) {return false; // 字符不存在}current = current->children[index];}return current->isEndOfWord; // 检查是否单词结束
}
3. 前缀检查
bool Trie::startsWith(string prefix) {TrieNode* current = root;for (char c : prefix) {int index = c - 'a';if (!current->children[index]) {return false;}current = current->children[index];}return true; // 前缀存在
}
四、复杂度分析
| 操作 | 时间复杂度 | 空间复杂度 | 
| 插入单词 | O(L) | O(L) | 
| 搜索单词 | O(L) | O(1) | 
| 前缀检查 | O(L) | O(1) | 
L = 字符串长度
五、实战应用:自动补全系统
// 获取以指定前缀开头的所有单词
vector<string> getSuggestions(TrieNode* root, string prefix) {vector<string> suggestions;TrieNode* current = root;// 定位到前缀的最后一个节点for (char c : prefix) {int index = c - 'a';if (!current->children[index]) {return suggestions; // 前缀不存在}current = current->children[index];}// DFS收集所有单词collectWords(current, prefix, suggestions);return suggestions;
}// DFS遍历收集单词
void collectWords(TrieNode* node, string currentWord, vector<string>& words) {if (!node) return;if (node->isEndOfWord) {words.push_back(currentWord);}for (int i = 0; i < ALPHABET_SIZE; i++) {if (node->children[i]) {char c = 'a' + i;collectWords(node->children[i], currentWord + c, words);}}
}
六、字典树的优化方向
-  内存优化:使用vector动态存储子节点(牺牲时间换空间) 
-  删除操作:添加引用计数支持安全删除 
-  压缩字典树:合并只有一个子节点的连续节点 
-  支持Unicode:使用哈希表替代固定数组 
七、总结与思考
字典树的核心价值在于解决字符串前缀匹配问题。相比哈希表:
| 特性 | 字典树 | 哈希表 | 
| 前缀搜索 | 高效支持 | 不支持 | 
| 内存占用 | 较高(空间换时间) | 较低 | 
| 查找效率 | O(L) | O(1)平均 | 
| 自动排序 | 支持 | 不支持 | 
适用场景选择:
-  需要前缀匹配 → 字典树 
-  仅需精确匹配 → 哈希表 
-  内存敏感场景 → 三向单词查找树 
纸上得来终觉浅,绝知此事要躬行!建议尝试实现字典树后,在LeetCode上练习相关题目(如208, 211, 212题)巩固理解。
扩展思考:如何修改字典树结构使其支持中文?欢迎在评论区分享你的思路!