基数树(Radix Tree),也称为紧凑前缀树(Compact Prefix Tree)或 Patricia 树(Patricia Tree),是一种用于存储和检索字符串键值的数据结构。基数树与Trie树(前缀树)类似,但通过压缩公共前缀来减少节点的数量,从而节省存储空间。基数树非常适合用于查找具有公共前缀的字符串。
基数树的特点
-
节点结构:
- 每个内部节点可以存储一个或多个字符,以及指向子节点的指针。
- 叶节点通常存储与键相关的值或者标记键的结束。
-
路径压缩:
- 如果一个节点只有一个子节点,则可以将这个子节点的路径压缩到父节点上,从而减少树的高度和节点数量。
- 通过路径压缩,基数树可以比普通的Trie树更紧凑。
-
高效存储:
- 由于路径压缩,基数树通常占用较少的内存空间。
- 适合存储具有大量公共前缀的字符串。
-
插入、查找和删除操作:
- 插入操作涉及创建新节点并可能需要路径压缩。
- 查找操作类似于Trie树,从根节点开始,沿着字符路径直到找到目标键。
- 删除操作需要调整树结构,可能包括路径压缩。
基数树的应用场景
-
路由表:
- 在网络路由表中,基数树可以用来存储IP地址前缀,并用于查找最具体的匹配项。
-
字典和词汇表:
- 在自然语言处理中,基数树可以用来存储词汇,尤其是当词汇具有大量共享前缀时。
-
文件系统:
- 文件系统中的目录和文件名可以使用基数树来组织和查找。
-
数据库索引:
- 数据库中的索引可以使用基数树来存储和查找键值。
基数树与Trie树的区别
-
节点结构:
- Trie树的每个节点通常只包含一个字符,并且有多个指向子节点的指针。
- 基数树的节点可以包含多个字符,并且可能只有单个子节点。
-
存储效率:
- Trie树可能会使用更多的内存空间,因为即使有多个字符串共享相同的前缀,每个字符仍然对应一个节点。
- 基数树通过路径压缩减少了节点数量,因此通常更节省空间。
-
性能:
- Trie树在最坏情况下可能需要更长的时间来查找键,因为它可能需要访问每个字符节点。
- 基数树通常提供更快的查找时间,因为它通过减少节点数量来提高效率。
实现示例
class RadixNode:
def __init__(self):
self.children = {}
self.is_end = False
class RadixTree:
def __init__(self):
self.root = RadixNode()
def insert(self, word):
node = self.root
while word:
for child in node.children:
if word.startswith(child):
if len(child) < len(word):
word = word[len(child):]
node = node.children[child]
break
elif len(child) == len(word):
node.children[child].is_end = True
return
else:
new_node = RadixNode()
node.children[word] = new_node
new_node.is_end = True
return
def search(self, word):
node = self.root
while word:
for child in node.children:
if word.startswith(child):
if len(child) < len(word):
word = word[len(child):]
node = node.children[child]
break
elif len(child) == len(word):
return node.children[child].is_end
else:
return False
return False
def delete(self, word):
def _delete(node, word):
if not word:
if node.is_end:
node.is_end = False
return len(node.children) == 0
return False
for child in node.children:
if word.startswith(child):
if len(child) < len(word):
should_delete = _delete(node.children[child], word[len(child):])
if should_delete:
del node.children[child]
return len(node.children) == 0 and not node.is_end
elif len(child) == len(word):
if node.children[child].is_end:
if not node.children[child].children:
del node.children[child]
else:
node.children[child].is_end = False
return len(node.children) == 0 and not node.is_end
return False
_delete(self.root, word)
def __str__(self):
def _str(node, prefix, is_last):
result = []
for i, (key, child) in enumerate(node.children.items()):
is_last_child = i == len(node.children) - 1
result.append(prefix)
result.append("└── " if is_last_child else "├── ")
result.append(key)
result.append("*" if child.is_end else "")
result.append("\n")
result.extend(_str(child, prefix + (" " if is_last_child else "│ "), is_last_child))
return result
return "".join(_str(self.root, "", True))
# 使用示例
tree = RadixTree()
words = ["romane", "romanus", "romulus", "rubens", "ruber", "rubicon", "rubicundus"]
for word in words:
tree.insert(word)
print("Radix Tree结构:")
print(tree)
print("\n搜索测试:")
print("romane:", tree.search("romane"))
print("romanus:", tree.search("romanus"))
print("romulus:", tree.search("romulus"))
print("ruber:", tree.search("ruber"))
print("ruby:", tree.search("ruby"))
print("\n删除 'romulus':")
tree.delete("romulus")
print(tree)
print("\n删除后搜索 'romulus':", tree.search("romulus"))
实现包含了以下主要功能:
- 插入(insert): 将单词插入到基数树中,实现了路径压缩。
- 搜索(search): 在基数树中搜索单词。
- 删除(delete): 从基数树中删除单词。
- 树结构可视化(str): 用于打印树的结构,方便调试和观察。
这个实现使用了路径压缩技术,即在可能的情况下合并节点以减少树的深度和节点数量。每个节点包含一个字典(children)来存储子节点,以及一个布尔值(is_end)来标记单词的结束。
使用到基数树的开源项目
1. Linux Kernel
Linux内核中使用了基数树来实现路由表和其他数据结构。Linux内核的路由表使用了radix-tree
模块来实现。
-
源码位置:
-
示例代码:
2. Redis
Redis 使用基数树来实现键值对的存储。Redis 的数据结构中使用了基数树来组织键的空间。
-
源码位置:
-
示例代码:
3. SQLite
SQLite 数据库使用了基数树来实现索引。
-
源码位置:
-
示例代码: