Trie
Trie(发音为 "try",字典树、前缀树)是一种专门用于高效处理字符串集合的 多叉树,用于存储字符串集合。字符存储在边上,从根节点到某个节点的路径上的所有字符连接起来,就是该节点对应的字符串前缀。
关键特性:
- 根节点不包含字符
- 从父节点到子节点的 边 代表一个字符
- 从根节点到某一节点的路径,连接起来就是该节点对应的字符串
- 每个节点的所有子节点对应的字符都不相同
图片来源: Wikipedia - Trie
由于 Trie 具有前缀搜索极快(\(O(m)\))、自动补全效率高、字符串排序天然有序等优点,在搜索引擎的自动补全、拼写检查、IP 路由等场景中有广泛应用。代价则是每个节点需要存储多个子节点指针所占据的空间。
| 操作 |
数组/列表 |
哈希表 |
Trie |
| 插入字符串 |
\(O(n \cdot m)\) |
\(O(m)\) |
\(O(m)\) |
| 查找字符串 |
\(O(n \cdot m)\) |
\(O(m)\) |
\(O(m)\) |
| 前缀搜索 |
\(O(n \cdot m)\) |
\(O(n \cdot m)\) |
\(O(m)\) |
| 空间复杂度 |
\(O(n \cdot m)\) |
\(O(n \cdot m)\) |
\(O(\text{ALPHABET\_SIZE} \cdot n \cdot m)\) |
其中 \(n\) 是字符串数量,\(m\) 是字符串平均长度。
实现
基本结构
| class TrieNode:
def __init__(self):
# 子节点字典: 字符 -> TrieNode
self.children = {}
# 标记是否为单词结尾
self.is_end_of_word = False
class Trie:
def __init__(self):
self.root = TrieNode()
|
核心操作
| def insert(self, word: str) -> None:
"""插入一个单词到 Trie 中"""
node = self.root
for char in word:
# 如果字符不存在,创建新节点
if char not in node.children:
node.children[char] = TrieNode()
# 移动到子节点
node = node.children[char]
# 标记单词结尾
node.is_end_of_word = True
|
时间复杂度: \(O(m)\),其中 \(m\) 是单词长度
空间复杂度: \(O(m)\)
| def search(self, word: str) -> bool:
"""查找单词是否存在于 Trie 中"""
node = self.root
for char in word:
if char not in node.children:
return False
node = node.children[char]
# 必须是完整单词,不能只是前缀
return node.is_end_of_word
|
时间复杂度: \(O(m)\)
空间复杂度: \(O(1)\)
| def starts_with(self, prefix: str) -> bool:
"""判断是否存在以 prefix 为前缀的单词"""
node = self.root
for char in prefix:
if char not in node.children:
return False
node = node.children[char]
# 只要能走到这里,说明前缀存在
return True
|
时间复杂度: \(O(m)\)
空间复杂度: \(O(1)\)
| def delete(self, word: str) -> bool:
"""删除一个单词"""
def _delete(node: TrieNode, word: str, index: int) -> bool:
if index == len(word):
# 到达单词末尾
if not node.is_end_of_word:
return False # 单词不存在
node.is_end_of_word = False
# 如果没有子节点,可以删除
return len(node.children) == 0
char = word[index]
if char not in node.children:
return False # 单词不存在
child = node.children[char]
should_delete_child = _delete(child, word, index + 1)
if should_delete_child:
del node.children[char]
# 如果当前节点不是单词结尾且没有其他子节点,可以删除
return not node.is_end_of_word and len(node.children) == 0
return False
return _delete(self.root, word, 0)
|
时间复杂度: \(O(m)\)
空间复杂度: \(O(m)\) (递归栈)
进阶操作
变体
典型应用场景
经典题目
优化技巧
对比其他数据结构
| 场景 |
推荐数据结构 |
原因 |
| 精确查找单词 |
哈希表 |
\(O(1)\) 查找 |
| 前缀搜索 |
Trie |
\(O(m)\) 前缀匹配 |
| 范围查询 |
平衡树 |
有序存储 |
| 模糊匹配 |
Trie + DFS |
支持通配符 |
| 后缀查询 |
后缀树/数组 |
专门优化 |