← 返回数据结构与算法目录
← 返回学习笔记首页
字典树(Trie)
数据结构与算法专题 · 高效字符串存储与检索
专题: 数据结构与算法系统学习
关键词: 数据结构, 算法, 字典树, Trie, 前缀树, Prefix Tree, 字符串匹配, 自动补全, 空间优化
一、概述
字典树(Trie),又称前缀树(Prefix Tree)或单词查找树,是一种用于高效存储和检索字符串集合的树形数据结构。它的核心思想是利用字符串之间的公共前缀来减少查询时间,最大限度地避免不必要的字符比较。Trie 的名字来源于"retrieval"(检索)的中间四个字母,由 Edward Fredkin 于 1960 年提出。
与平衡树和哈希表相比,Trie 在处理字符串前缀匹配、自动补全和拼写检查等场景中具有天然优势。在搜索引擎的自动补全提示、手机输入法的联想词、路由器的 IP 路由查找、以及基因序列分析等领域,Trie 及其变体都是不可或缺的核心数据结构。
核心特点: Trie 以空间换时间,利用字符的公共前缀合并存储路径,使具有相同前缀的字符串共享存储空间。查找任意一个长度为 m 的字符串,仅需 O(m) 时间,与集合中元素总数 N 无关。
形象理解: 如果我们要存储 {"cat", "car", "dog"} 这三个单词,在 Trie 中,"ca" 这个前缀只存储一次,后面分叉为 "t" 和 "r" 两个分支。"dog" 则是另一条完全独立的路径。这种结构使得查找所有以 "ca" 开头的单词变得极其迅速——只需沿着路径找到 "ca" 节点,然后遍历其子树即可。
二、核心概念与节点设计
Trie 的每个节点代表一个字符(更准确地说,代表从根到当前节点的路径所对应的前缀)。根节点不包含任何字符,代表空字符串。每个节点通常包含两个核心部分:一个指向子节点的映射结构(children),以及一个标记当前节点是否为某个单词结尾的布尔值(is_end)。
节点定义
在 Python 中,Trie 节点的最典型实现使用字典(dict)来存储子节点映射,因为字典支持 O(1) 的键值查找,且可以动态增长,非常适合字符集不确定的场景。
class TrieNode:
"""字典树节点"""
def __init__(self):
# 子节点映射:字符 -> TrieNode
self.children = {}
# 标记当前节点是否为某个单词的结尾
self.is_end = False
# 可选:记录经过该节点的单词数量(用于词频统计)
self.count = 0
# 可选:存储该前缀对应的完整单词(用于自动补全)
self.word = None
完整的 Trie 类骨架
基于上述节点定义,我们可以构建 Trie 类。下面展示完整的类结构,后续各节将逐一填充每个方法的实现细节。
class Trie:
"""字典树"""
def __init__(self):
self.root = TrieNode()
def insert(self, word: str) -> None:
"""向 Trie 中插入一个单词"""
pass # 详见 三、基本操作与实现
def search(self, word: str) -> bool:
"""搜索单词是否完整存在"""
pass
def starts_with(self, prefix: str) -> bool:
"""判断是否存在以 prefix 为前缀的单词"""
pass
def delete(self, word: str) -> bool:
"""从 Trie 中删除一个单词"""
pass
def get_all_words(self) -> list:
"""获取 Trie 中所有单词"""
pass
def autocomplete(self, prefix: str) -> list:
"""获取所有以 prefix 为前缀的单词"""
pass
三、基本操作与实现
3.1 插入操作
插入操作的核心逻辑是:从根节点开始,逐字符遍历待插入的单词。对于每个字符,如果当前节点不存在对应字符的子节点,则创建一个新的 TrieNode 并将其添加到 children 字典中。然后移动到该子节点继续处理下一个字符。当单词的所有字符都处理完毕后,将最后一个节点的 is_end 标记设为 True,表示该单词已完整插入 Trie。
def insert(self, word: str) -> None:
node = self.root
for ch in word:
# 如果当前字符不存在于子节点中,创建新节点
if ch not in node.children:
node.children[ch] = TrieNode()
# 移动到下一个节点
node = node.children[ch]
# 标记当前节点为单词结尾
node.is_end = True
说明: 如果单词已存在(即所有字符路径都存在且最后的 is_end 已为 True),重复插入不会创建新节点,只是将 is_end 再次设置为 True(幂等操作)。但如果路径不存在,插入会创建所需的全部路径节点。
3.2 搜索操作
搜索操作和插入非常相似,同样是逐字符在 Trie 中查找。不同之处在于:如果在某个字符处发现 child 不存在,则单词肯定不在 Trie 中,返回 False。如果所有字符都成功匹配,还需要检查最后节点的 is_end 标记——只有 is_end 为 True,才说明该单词作为一个完整单词被插入过。
def search(self, word: str) -> bool:
node = self.root
for ch in word:
# 字符路径不存在,单词不在 Trie 中
if ch not in node.children:
return False
node = node.children[ch]
# 必须检查 is_end,因为该路径可能只是一个前缀
return node.is_end
注意: 假设我们插入了 "apple" 但没有插入 "app"。搜索 "app" 时,路径是存在的(因为它是 "apple" 的前缀),但 "app" 的 is_end 为 False,所以 search("app") 返回 False。这就要求在设计时,如果需要同时保留前缀和完整单词,插入 "app" 时需要专门将其 is_end 设为 True。
3.3 前缀搜索
前缀搜索是 Trie 最核心的优势操作。它与搜索的区别在于:不需要检查最终节点的 is_end 标记,只要路径存在就返回 True。这意味着我们只关心是否存在以该字符串为前缀的任意单词,而不关心该字符串本身是否是一个完整单词。
def starts_with(self, prefix: str) -> bool:
node = self.root
for ch in prefix:
if ch not in node.children:
return False
node = node.children[ch]
# 路径存在即返回 True,不检查 is_end
return True
3.4 删除操作
删除操作比插入和搜索更复杂,需要处理多种情况。基本思路是递归地进行删除:如果待删除的单词存在,将路径上最后一个节点的 is_end 设为 False。为了节省空间,可以进一步清理那些不再被其他单词使用的节点——即某个节点没有子节点且 is_end 为 False 时,可以安全地将其从父节点的 children 中删除。
def delete(self, word: str) -> bool:
"""删除单词,成功返回 True,单词不存在返回 False"""
def _delete(node: TrieNode, word: str, depth: int) -> bool:
"""递归删除,返回是否应删除当前节点(剪枝)"""
if depth == len(word):
# 单词不存在
if not node.is_end:
return False
# 标记为非结尾
node.is_end = False
# 如果没有子节点,可以删除此节点
return len(node.children) == 0
ch = word[depth]
if ch not in node.children:
return False # 单词不存在
# 递归处理下一个字符
should_delete_child = _delete(node.children[ch], word, depth + 1)
if should_delete_child:
del node.children[ch]
# 如果当前节点也不是单词结尾且没有其他子节点,可以删除
return not node.is_end and len(node.children) == 0
return False
return _delete(self.root, word, 0)
删除策略总结: (1)如果待删除单词是其他单词的前缀(如删除 "app" 但 "apple" 还在),只需将 is_end 设为 False,保留路径。(2)如果待删除单词没有与其他单词共享路径(如删除一个独立插入的 "dog"),可以回收整条路径上的无用节点。(3)如果待删除单词与其他单词共享部分前缀(如删除 "cat" 但 "car" 还在),只删除共享路径之后的独有节点。
3.5 获取所有单词
通过 DFS(深度优先搜索)遍历 Trie 的所有节点,每当遇到 is_end 为 True 的节点,就记录下从根到该节点路径上字符拼接而成的完整单词。
def get_all_words(self) -> list:
"""返回 Trie 中存储的所有单词"""
result = []
def dfs(node: TrieNode, path: list):
if node.is_end:
result.append(''.join(path))
for ch, child in node.children.items():
path.append(ch)
dfs(child, path)
path.pop()
dfs(self.root, [])
return result
3.6 自动补全
自动补全是前缀搜索的扩展应用。先定位到前缀对应的最后一个节点,然后从该节点开始 DFS 遍历以该节点为根的子树,收集所有完整单词。
def autocomplete(self, prefix: str) -> list:
"""获取所有以 prefix 为前缀的单词"""
node = self.root
# 先定位到前缀末尾
for ch in prefix:
if ch not in node.children:
return [] # 前缀不存在
node = node.children[ch]
# 从前缀末尾节点开始 DFS
result = []
path = list(prefix)
def dfs(node: TrieNode):
if node.is_end:
result.append(''.join(path))
for ch, child in node.children.items():
path.append(ch)
dfs(child)
path.pop()
dfs(node)
return result
四、时间复杂度分析
在分析 Trie 的时间复杂度时,关键变量有两个:待操作字符串的长度 m 和 Trie 中存储的字符串总数 N。
操作 时间复杂度 说明
插入 O(m) 遍历字符串每个字符,每步查找/创建子节点均为 O(1)(哈希表)
搜索 O(m) 沿路径逐字符查找,检查 is_end
前缀搜索 O(m) 沿路径逐字符查找,无需检查 is_end
删除 O(m) 沿路径找到末尾后递归回退清理
获取所有单词 O(N * L̄) DFS 遍历所有节点,N 为单词数,L̄ 为平均长度
自动补全 O(m + k) m 为前缀查找,k 为补全结果总长度
时间复杂度对比: 与哈希表相比,Trie 的前缀搜索操作(starts_with)是天然支持的 O(m) 操作,而哈希表需要遍历所有键执行前缀匹配,时间复杂度为 O(N * m)。但在完全匹配的搜索场景中,哈希表可以达到平均 O(1)(理想情况下优于 Trie 的 O(m))。然而哈希表有哈希冲突的潜在风险,且无法支持有序遍历。
五、空间优化
Trie 的主要缺点是空间消耗较大。在最坏情况下(如很少或没有公共前缀的字符串集合),每个字符都需要一个独立的 TrieNode,每个节点又需要维护一个 children 映射结构。以下是几种常见的空间优化策略。
5.1 哈希表 vs 固定数组
Trie 节点的 children 有两种主流实现方式:哈希表(dict)和固定长度数组。二者各有优劣。
哈希表实现(推荐)
只存储实际出现的字符分支
内存效率高,尤其适合稀疏场景
支持任意 Unicode 字符集
Python 中更自然
固定数组实现
预分配 26/52/256 个 slot
访问更快(数组索引 O(1))
字符集受限(如仅限小写字母)
大量内存浪费(空 slot 占位)
# 固定数组实现(仅适用于小写字母 a-z)
class ArrayTrieNode:
def __init__(self):
self.children = [None] * 26 # 预分配 26 个 slot
self.is_end = False
def _index(self, ch: str) -> int:
return ord(ch) - ord('a') # 'a' -> 0, 'b' -> 1, ..., 'z' -> 25
5.2 压缩 Trie(Radix Tree / Patricia Trie)
压缩 Trie 的核心思想是:将连续单子节点(即某个节点只有一个子节点,且该节点不是单词结尾)合并为一条边上的字符串。这样显著减少了节点数量,大幅降低内存占用。
对比示例: 存储 "abandon" 和 "abnormal" 时,普通 Trie 需要为 "a" → "b" → "a" → "n" 各创建独立节点,而压缩 Trie 将共享前缀 "ab" 存储为一条边,后面分叉为 "andon" 和 "normal" 两条边。节点数从 9 个减少到 4 个。
class RadixNode:
"""基数树(压缩Trie)节点"""
def __init__(self):
# 边:前缀字符串 -> RadixNode
self.edges = {}
self.is_end = False
class RadixTree:
"""基数树 / 压缩字典树"""
def __init__(self):
self.root = RadixNode()
def insert(self, word: str) -> None:
# 实现略:需要处理边的分割和合并
# 核心逻辑:查找最长公共前缀,分割已有边,创建新分支
pass
5.3 其他优化技术
三级 Trie(Ternary Search Trie): 每个节点包含三个子节点(小于、等于、大于),结合了二叉搜索树的空间效率和 Trie 的前缀查询速度。
Double-Array Trie(双数组字典树): 使用两个数组(base 和 check)以紧凑形式表示 Trie,广泛应用于自然语言处理中的分词系统。
尾缀共享: 在空间极度敏感的场景中,可以让单词的尾缀部分共享存储(即多个单词的末尾共享同一段字符串)。
六、压缩变体:基数树(Radix Tree)
基数树(Radix Tree),又称 Patricia Trie(Practical Algorithm to Retrieve Information Coded in Alphanumeric),是 Trie 的一种重要变体。它将 Trie 中连续的单子节点路径压缩为一条边上的字符串标签,从而显著减少了节点数量和层级深度。
核心思想
在普通 Trie 中,路径上的每一个字符都对应一个独立的节点。如果存储的字符串之间共享的前缀很短(或没有共享前缀),Trie 的节点数几乎等于所有字符串的总字符数,空间效率很低。Radix Tree 通过"边压缩"策略,将连续的"单子节点链"合并为一个节点和一条字符串边,同一层级可以有多个字符作为边标签。
与普通 Trie 的对比
特性 普通 Trie Radix Tree
节点数量 ≈ 所有字符串总字符数 ≈ 所有字符串数量 + 分叉点数量
边标签 单个字符 字符串(可包含多个字符)
插入复杂度 O(m),简单 O(m),需处理边分割,较复杂
空间效率 低(节点过多) 高(节点大幅减少)
查询效率 O(m),逐字符 O(m),按串匹配(实际比较更快)
典型应用: Linux 内核中的路由表使用 Radix Tree 进行 IP 路由查找;Redis 中的字典实现使用了压缩 Trie 的思想来加速键的查找;Nginx 中的地理位置数据库也采用类似的数据结构进行高效的前缀匹配。
七、应用场景
7.1 自动补全(Auto-complete)
这是 Trie 最经典的应用。搜索引擎(如 Google)、代码编辑器(如 VS Code、IntelliJ)、手机输入法都使用 Trie 来实现关键字或代码的自动补全提示。基本原理就是用 Trie 存储热门查询词或代码片段,用户输入前缀时快速检索所有匹配的候选项。
def top_k_autocomplete(self, prefix: str, k: int) -> list:
"""返回前 k 个最热门的前缀匹配建议"""
candidates = self.autocomplete(prefix)
# 按频率降序排列(需要节点中存储频率信息)
candidates.sort(key=lambda w: self.get_frequency(w), reverse=True)
return candidates[:k]
7.2 拼写检查(Spell Check)
利用 Trie 实现拼写检查时,首先将词典中的所有正确单词构建成 Trie。用户输入的每个单词通过 search 方法检查是否存在于 Trie 中。如果不存在,可以通过编辑距离(Levenshtein Distance)结合 Trie 来查找最相似的候选词——即从 Trie 中找出与输入单词编辑距离最小的若干单词。
def spell_check(self, word: str, max_distance: int = 2) -> list:
"""基于编辑距离的拼写纠错"""
result = []
def dfs(node: TrieNode, path: list, i: int, edits: list):
"""带编辑距离计算的 Trie 递归搜索"""
if i == len(word) and node.is_end:
candidate = ''.join(path)
dist = edits[-1][-1] if edits else 0
if dist <= max_distance:
result.append((candidate, dist))
return
# 剪枝:当前编辑距离已经超过阈值
# (具体实现涉及 DP 矩阵传递,此处为简化示意)
for ch, child in node.children.items():
path.append(ch)
dfs(child, path, i + 1 if i < len(word) and word[i] == ch else i, edits)
path.pop()
dfs(self.root, [], 0, None)
return sorted(result, key=lambda x: x[1])
7.3 词频统计
在 Trie 节点中增加 count 字段,记录经过该节点的单词数量。插入单词时,路径上所有节点的 count 都加 1。这样不仅可以快速查询某个单词的词频,还可以统计以某个前缀开头的所有单词的总词频。
def insert_with_freq(self, word: str):
"""插入单词并更新路径上的词频计数"""
node = self.root
for ch in word:
if ch not in node.children:
node.children[ch] = TrieNode()
node = node.children[ch]
node.count += 1 # 经过该前缀的单词数加 1
node.is_end = True
def prefix_frequency(self, prefix: str) -> int:
"""查询以 prefix 为前缀的单词总数"""
node = self.root
for ch in prefix:
if ch not in node.children:
return 0
node = node.children[ch]
return node.count
7.4 IP 路由最长前缀匹配
在计算机网络中,IP 路由表使用 CIDR(无类别域间路由)表示路由规则。路由器需要根据目标 IP 地址在路由表中找到最长的匹配前缀。将路由前缀构建成 Trie(二进制 Trie),每个节点对应 IP 地址的一位(0 或 1),包含路由信息的节点标记为有效。查找时深度优先搜索,返回最深的有效匹配节点,即可实现最长前缀匹配。
class BinaryTrieNode:
"""二进制Trie节点(用于IP路由)"""
def __init__(self):
self.children = [None, None] # 0 和 1
self.is_route = False
self.next_hop = None # 下一跳信息
class IPRoutingTable:
"""基于二进制 Trie 的路由表"""
def __init__(self):
self.root = BinaryTrieNode()
def add_route(self, ip: str, prefix_len: int, next_hop: str):
"""添加路由规则(如 192.168.1.0/24 -> gateway)"""
node = self.root
binary_ip = self._ip_to_bits(ip, prefix_len)
for bit in binary_ip:
if not node.children[bit]:
node.children[bit] = BinaryTrieNode()
node = node.children[bit]
node.is_route = True
node.next_hop = next_hop
def lookup(self, ip: str) -> str:
"""最长前缀匹配查找"""
node = self.root
result = None
binary_ip = self._ip_to_bits(ip, 32)
for bit in binary_ip:
if not node.children[bit]:
break
node = node.children[bit]
if node.is_route:
result = node.next_hop # 更新为更长的匹配
return result
def _ip_to_bits(self, ip: str, length: int) -> list:
parts = list(map(int, ip.split('.')))
bits = []
for p in parts:
for i in range(7, -1, -1):
bits.append((p >> i) & 1)
return bits[:length]
7.5 其他应用
文本压缩算法: LZW(Lempel-Ziv-Welch)算法使用 Trie 来构建动态字典,实现高效的文本压缩。
基因组学: DNA 序列可以被视为由 A、T、C、G 四种碱基组成的字符串。Trie 及其变体常用于基因序列的索引和模式匹配。
自然语言处理: 分词系统(如 jieba 分词)使用 Trie 来高效加载和检索词典,支持最大正向/逆向匹配算法。
敏感词过滤: 将敏感词库构建成 Trie,对输入文本进行扫描时,可以在 O(n) 时间内检测所有敏感词出现的位置和类型。
八、Trie vs 哈希表对比
在字符串存储和检索的场景中,Trie 和哈希表(Hash Table / 哈希集合)是两种最常用的数据结构。了解它们的适用场景对于做出正确的设计选择至关重要。
维度 Trie 哈希表
单字符串搜索 O(m),稳定 O(1) 平均,O(N) 最坏(冲突)
前缀搜索 O(m),天然支持 O(N * m),需遍历所有键
有序遍历 O(N * L̄),天然有序(字典序) O(N log N),需额外排序
空间效率 较差(尤其短字符串) 较好,但有空槽浪费
哈希冲突 无(路径唯一) 有,需解决冲突
删除操作 O(m),需处理剪枝 O(1) 平均
字符集支持 灵活(Unicode 任意) 灵活(键值 hashable 即可)
动态扩展 自然支持 需 rehash/扩容
选择建议: 应用场景涉及前缀匹配或需要按字典序遍历字符串时,应优先选择 Trie。仅需要精确的键值查询且对空间有要求的场景,应优先选择哈希表。如果数据量极小(如少于 100 个短字符串),哈希表通常是更简单高效的方案。
"Trie 的真正价值不在于它能做哈希表也能做的事情,而在于它能做哈希表做不了的事情——前缀匹配。"
九、完整代码示例
下面给出一个整合了所有核心功能的 Trie 完整实现,包含类型注解和详细的文档字符串,可以直接在项目中使用。
class TrieNode:
def __init__(self):
self.children = {}
self.is_end = False
self.count = 0
self.word = None
class Trie:
"""字典树(Trie / Prefix Tree)完整实现"""
def __init__(self):
self.root = TrieNode()
def insert(self, word: str) -> None:
"""向 Trie 中插入一个单词"""
node = self.root
for ch in word:
if ch not in node.children:
node.children[ch] = TrieNode()
node = node.children[ch]
node.count += 1
node.is_end = True
node.word = word
def search(self, word: str) -> bool:
"""查询单词是否完整存在于 Trie 中"""
node = self.root
for ch in word:
if ch not in node.children:
return False
node = node.children[ch]
return node.is_end
def starts_with(self, prefix: str) -> bool:
"""判断是否存在以 prefix 为前缀的单词"""
node = self.root
for ch in prefix:
if ch not in node.children:
return False
node = node.children[ch]
return True
def delete(self, word: str) -> bool:
"""从 Trie 中删除单词(成功返回 True)"""
def _delete(node, w, d):
if d == len(w):
if not node.is_end:
return False
node.is_end = False
node.word = None
return len(node.children) == 0
ch = w[d]
if ch not in node.children:
return False
should_del = _delete(node.children[ch], w, d + 1)
if should_del:
del node.children[ch]
return not node.is_end and len(node.children) == 0
return _delete(self.root, word, 0)
def autocomplete(self, prefix: str) -> list:
"""获取所有以 prefix 为前缀的单词(自动补全)"""
node = self.root
for ch in prefix:
if ch not in node.children:
return []
node = node.children[ch]
result = []
def dfs(n, path):
if n.is_end:
result.append(''.join(path))
for ch, child in n.children.items():
path.append(ch)
dfs(child, path)
path.pop()
dfs(node, list(prefix))
return result
def count_prefix(self, prefix: str) -> int:
"""统计以 prefix 为前缀的单词数量"""
node = self.root
for ch in prefix:
if ch not in node.children:
return 0
node = node.children[ch]
return node.count
def __contains__(self, word: str) -> bool:
return self.search(word)
def __len__(self) -> int:
return self.root.count
使用示例
# ============ 使用示例 ============
trie = Trie()
# 插入单词
words = ["apple", "app", "application", "apricot", "banana"]
for w in words:
trie.insert(w)
# 搜索
print(trie.search("apple")) # True
print(trie.search("app")) # True(单独插入过)
print(trie.search("apt")) # False
# 前缀搜索
print(trie.starts_with("app")) # True
print(trie.starts_with("appl")) # True
print(trie.starts_with("xy")) # False
# 自动补全
print(trie.autocomplete("app"))
# ['app', 'apple', 'application']
# 删除
trie.delete("app")
print(trie.search("app")) # False
print(trie.search("apple")) # True(不受影响)
# 前缀统计
print(trie.count_prefix("ap")) # 2(apple, application)
# 遍历
print(trie.autocomplete(""))
# ['apple', 'application', 'apricot', 'banana']
十、核心要点总结
知识卡片
本质定义: Trie 是一种多叉树结构,利用字符串公共前缀减少查询时间,将具有相同前缀的字符串共享路径存储。
节点设计: 每个节点包含 children 映射(哈希表/数组)和 is_end 标记。必要时可增加 count、word 等辅助字段以支持词频统计和快捷返回。
核心操作: 插入和搜索都是 O(m) 时间,其中 m 为字符串长度;前缀搜索是 Trie 的标志性优势操作,同样是 O(m) 时间。
空间权衡: Trie 以空间换时间,节点数庞大时内存开销较高。可通过固定数组、压缩 Trie(Radix Tree)、双数组 Trie 等方式优化。
适用场景: 自动补全(前缀匹配)、拼写检查(编辑距离+Trie)、词频统计、IP 路由(最长前缀匹配)、敏感词过滤、基因组模式匹配。
选型决策: 需要前缀匹配或有序遍历时选 Trie,仅需要精确查询且对空间敏感时选哈希表。
压缩变体: Radix Tree / Patricia Trie 将连续单子节点路径压缩为字符串边,大幅减少节点数,是工业界的常用优化方案。
十一、进一步思考
Trie 虽然优雅且高效,但在大规模数据场景下仍需慎重设计。以下是一些值得深入思考的方向:
工程实践中的权衡: 在构建一个覆盖百万级别词条的搜索引擎自动补全系统时,需要考虑以下问题:(1)内存限制:百万词条的原生 Trie 可能消耗数百 MB 甚至数 GB 内存,需要评估 Radix Tree 或 Double-Array Trie 的适用性。(2)动态更新:如果词典需要频繁增删(如在线学习系统),Trie 的动态更新开销是否可接受?(3)并发安全:多线程环境下 Trie 的读写是否需要加锁?能否采用无锁设计?(4)持久化:如何将 Trie 序列化到磁盘并在启动时快速加载?
扩展研究方向: (1)Succinct Trie(简洁字典树)——使用位图等紧凑表示法将 Trie 的空间开销压缩到接近信息论下界。(2)HAT-Trie —— 结合哈希表和 Trie 的混合数据结构,在缓存效率和查询速度上做出更好的平衡。(3)Adaptive Radix Tree(ART,自适应基数树)——根据节点密度动态选择不同的内部表示,在空间和速度之间取得最佳平衡,是近年来被广泛研究的高性能索引结构。
"理解 Trie 不仅仅是学会一种数据结构,更是理解"如何利用数据的结构特征来加速计算"这一计算机科学的核心思想。字符串的公共前缀正是数据的结构特征,Trie 将其显式地编码到了数据结构中。"
LeetCode 推荐练习
为巩固对 Trie 的理解,推荐按以下顺序练习 LeetCode 相关题目:
LeetCode 208 - 实现 Trie(前缀树):基础题,实现 insert、search、startsWith 三个核心方法。
LeetCode 720 - 词典中最长的单词:综合利用 Trie 的插入和搜索操作。
LeetCode 676 - 实现一个魔法字典:Trie + 编辑距离搜索。
LeetCode 211 - 添加与搜索单词:支持通配符 "." 的搜索,考察 Trie + DFS 的结合。
LeetCode 212 - 单词搜索 II:Trie + 回溯的经典组合,高频面试题。
LeetCode 421 - 数组中两个数的最大异或值:利用二进制 Trie 求解最大异或对。
LeetCode 745 - 前缀和后缀搜索:双 Trie 结合的设计题。