数据结构编程实践20讲(Python版)—19字典树

ops/2024/10/25 0:59:05/

本文目录

    • 19 字典树(Trie)
      • S1 说明
        • 字典树结构
        • 字典树的构建与查找
        • 字典树的特点
        • 字典树的应用领域
      • S2 示例
      • S3 应用1:基于 big.txt 实现单词的自动补全功能
      • S3 应用2:实现 IP 路由中的最长前缀匹配
      • S3 应用3:基于 Trie 的压缩算法(LZW 算法)

往期链接

01 数组02 链表03 栈04 队列05 二叉树06 二叉搜索树07 AVL树08 红黑树09 B树10 B+树
11 线段树12 树状数组13 图形数据结构14 邻接矩阵15 完全图16 有向图17 散列18 哈希表

19 字典树(Trie)

S1 说明

字典树,又称为前缀树,是一种树形数据结构,主要用于存储字符串集合,用于高效地完成字符串的插入和查找操作。树的核心思想是利用字符串的公共前缀 来减少存储空间和查找时间。

字典树结构
  • 节点(Node):每个节点代表一个字符串中的一个字符。
  • 边(Edge):从父节点到子节点的连接,表示字符的连接关系。
  • 根节点(Root):一个空节点,代表空字符串的开始。
  • 结束标志(Terminal Flag):用于标识某个节点是否为某个字符串的结尾。
字典树的构建与查找
  • 构建(Insertion)
    • 从根节点开始。
    • 对于要插入的字符串,从第一个字符开始,逐个字符向下寻找对应的子节点。
    • 如果子节点存在,则移动到子节点;否则,创建新的子节点。
    • 重复上述步骤,直到处理完字符串的所有字符。
    • 标记当前节点为结束标志,表示一个完整字符串的结束。
  • 查找(Search)
    • 从根节点开始。
    • 按照要查找的字符串,从第一个字符开始,逐个字符向下寻找对应的子节点。
    • 如果在某一步无法找到对应的子节点,则表示该字符串不在Trie中。
    • 如果成功找到所有字符对应的节点,并且最后的节点标记为结束标志,则表示Trie中存在该字符串。
字典树的特点

1. 优势

  • 高效的字符串查询:Trie树可以在 O(m) 的时间内完成字符串的插入、删除和查找操作,其中 m 为字符串的长度。这与集合中元素的数量无关,避免了传统搜索算法中 O(log n) 或 O(n) 的时间复杂度。
  • 前缀匹配:Trie天然支持前缀查询,可以方便地实现以某个前缀开头的所有字符串的检索。
  • 节省存储空间:通过共享公共前缀,Trie可以减少存储重复的字符,尤其在字符串集合存在大量公共前缀的情况下。

2. 劣势

  • 空间占用较大:对于字符集较大的情况(如UNICODE字符集),Trie的节点分支数会很大,可能导致空间浪费。
  • 实现复杂度:相比于哈希表等数据结构,Trie的实现要复杂一些,维护起来也更为繁琐。
  • 不支持部分操作:Trie不适合用于需要对字符串进行排序或范围查询的场景,因为它不维护元素的顺序关系。
字典树的应用领域

1. 字符串检索与自动补全

  • 输入法:在输入法中,利用Trie可以高效地完成对用户输入前缀的匹配,提供候选词的自动补全。
  • 搜索引擎:提供搜索关键词的实时提示功能,根据用户输入的前缀,快速返回可能的搜索词。

2. 词典存储与拼写检查

  • 拼写检查器:将词典中的所有单词存储在Trie中,可以快速判断一个单词是否正确,以及提供可能的拼写建议。
  • 敏感词过滤:利用Trie存储敏感词汇,在文本处理中快速检测并屏蔽敏感词。

3. 路由表和前缀匹配

  • 网络路由表:在网络路由中,使用Trie(如Prefix Tree,前缀树)实现最长前缀匹配,快速确定数据包的转发路径。

4. 字符串统计与分析

  • 统计字符串出现次数:在Trie的节点中维护计数器,可以统计字符串或前缀的出现频率,用于文本分析和数据挖掘。

5. DNA序列分析

  • 生物信息学:DNA序列由A、C、G、T组成,利用Trie可以高效地存储和检索DNA序列片段,进行模式匹配和序列分析。

6. 压缩算法

  • 压缩算法中的字典构建:如LZ前缀编码算法,利用Trie构建编码字典,实现数据的压缩。

7. 多模式串匹配

  • Aho-Corasick算法:构建Trie树并结合失败指针,实现同时匹配多种模式串,应用于病毒检测、文本搜索等领域。

S2 示例

python">class TrieNode:"""Trie 树的节点"""def __init__(self):self.children = {}  # 子节点字典,键为字符,值为 TrieNodeself.is_end_of_word = False  # 标记是否为单词的结尾class Trie:"""Trie 树"""def __init__(self):self.root = TrieNode()def insert(self, word):"""在 Trie 中插入一个单词"""node = self.rootfor char in word:if char not in node.children:# 如果子节点中没有当前字符,创建一个新的 TrieNodenode.children[char] = TrieNode()node = node.children[char]# 单词结束,标记结尾node.is_end_of_word = Truedef search(self, word):"""在 Trie 中搜索一个单词"""node = self.rootfor char in word:if char not in node.children:return False  # 未找到当前字符,返回 Falsenode = node.children[char]return node.is_end_of_word  # 检查是否为单词的结尾def starts_with(self, prefix):"""判断 Trie 中是否存在以指定前缀 prefix 开头的单词"""node = self.rootfor char in prefix:if char not in node.children:return False  # 未找到当前前缀node = node.children[char]return True  # 找到前缀def _traverse(self, node, prefix, words):"""辅助函数,用于深度优先遍历 Trie,收集单词"""if node.is_end_of_word:words.append(prefix)for char, child_node in node.children.items():self._traverse(child_node, prefix + char, words)def get_words_with_prefix(self, prefix):"""获取 Trie 中所有以指定前缀 prefix 开头的单词"""node = self.rootfor char in prefix:if char not in node.children:return []  # 前缀不存在,返回空列表node = node.children[char]words = []self._traverse(node, prefix, words)return words# 测试代码
if __name__ == "__main__":trie = Trie()# 插入单词列表words_to_insert = ["apple", "app", "apply", "apt", "bat", "ball", "banana"]for word in words_to_insert:trie.insert(word)# 测试搜索单词words_to_search = ["app", "apple", "apt", "apart", "batman", "banana"]for word in words_to_search:found = trie.search(word)print(f"单词 '{word}' 在 Trie 中{'存在' if found else '不存在'}。")# 测试前缀查询prefixes = ["app", "ba", "cat"]for prefix in prefixes:has_prefix = trie.starts_with(prefix)print(f"Trie 中{'存在' if has_prefix else '不存在'}以 '{prefix}' 为前缀的单词。")if has_prefix:words_with_prefix = trie.get_words_with_prefix(prefix)print(f"以 '{prefix}' 为前缀的单词有:{words_with_prefix}")

结果

python">单词 'app' 在 Trie 中存在。
单词 'apple' 在 Trie 中存在。
单词 'apt' 在 Trie 中存在。
单词 'apart' 在 Trie 中不存在。
单词 'batman' 在 Trie 中不存在。
单词 'banana' 在 Trie 中存在。
Trie 中存在以 'app' 为前缀的单词。
以 'app' 为前缀的单词有:['app', 'apple', 'apply']
Trie 中存在以 'ba' 为前缀的单词。
以 'ba' 为前缀的单词有:['bat', 'ball', 'banana']
Trie 中不存在以 'cat' 为前缀的单词。

S3 应用1:基于 big.txt 实现单词的自动补全功能

需要下载包含大量英文单词和语料的 big.txt 文件,才能正常运行程序。该文件下载地址:big.txt

python">import re
from collections import defaultdictclass TrieNode:"""Trie 树的节点"""def __init__(self):self.children = {}  # 子节点self.is_end_of_word = False  # 是否为完整单词self.frequency = 0  # 词频,用于排序self.word = None  # 完整单词class AutoCompleteTrie:"""自动补全功能的 Trie 树"""def __init__(self):self.root = TrieNode()def insert(self, word, frequency=1):"""插入单词及其词频"""node = self.rootfor char in word:if char not in node.children:node.children[char] = TrieNode()node = node.children[char]node.is_end_of_word = Truenode.frequency += frequencynode.word = worddef search(self, prefix):"""查找所有以 prefix 为前缀的单词"""node = self.rootfor char in prefix:if char not in node.children:return []  # 无匹配的前缀,返回空列表node = node.children[char]# 使用优先队列(堆)存储匹配的单词,按照词频排序words = []self._dfs(node, words)# 按照词频从高到低排序words.sort(key=lambda x: (-x[1], x[0]))return [word for word, freq in words]def _dfs(self, node, words):"""深度优先搜索,收集单词及其词频"""if node.is_end_of_word:words.append((node.word, node.frequency))for child in node.children.values():self._dfs(child, words)def preprocess_text(file_path):"""读取文本文件,提取单词及其出现频率"""word_freq = defaultdict(int)with open(file_path, 'r', encoding='utf-8') as f:for line in f:# 使用正则表达式提取单词,忽略大小写words = re.findall(r'\b[a-z]+\b', line.lower())for word in words:word_freq[word] += 1return word_freq# 主程序
if __name__ == "__main__":trie = AutoCompleteTrie()# 从 big.txt 文件中提取单词及其频率word_freq_dict = preprocess_text('big.txt')print("正在构建 Trie 树,请稍候...")# 将单词及词频插入 Trie 树for word, freq in word_freq_dict.items():trie.insert(word, freq)print("Trie 树构建完成!")# 进入交互式查询while True:prefix = input("请输入搜索前缀(输入'exit'退出):").strip().lower()if prefix == 'exit':breaksuggestions = trie.search(prefix)if suggestions:print("自动补全建议:")for word in suggestions[:10]:  # 只显示前 10 个建议print(f"- {word}")else:print("未找到匹配的建议。")

结果

python">正在构建 Trie 树,请稍候...
Trie 树构建完成!
请输入搜索前缀(输入'exit'退出):ty
自动补全建议:
- type
- typical
- ty
- types
- typically
- tyranny
- tyrant
- tyre
请输入搜索前缀(输入'exit'退出):an
自动补全建议:
- an
- anger
- answer
- analogy
- analysis
- ancestor
- ancient
- and
- angel
- angle
请输入搜索前缀(输入'exit'退出):exit

S3 应用2:实现 IP 路由中的最长前缀匹配

python">class TrieNode:"""Trie 树的节点,用于 IP 前缀匹配"""def __init__(self):self.children = {}self.next_hop = None  # 保存路由的下一跳信息class IPRoutingTrie:"""IP 路由的 Trie 实现"""def __init__(self):self.root = TrieNode()def insert(self, ip_prefix, next_hop):"""插入路由前缀:param ip_prefix: 形如 '192.168.0.0/16':param next_hop: 下一跳信息"""ip, prefix_length = ip_prefix.split('/')prefix_length = int(prefix_length)binary_ip = self._ip_to_binary(ip)node = self.rootfor bit in binary_ip[:prefix_length]:if bit not in node.children:node.children[bit] = TrieNode()node = node.children[bit]node.next_hop = next_hopdef search(self, ip_address):"""查找目的 IP 地址的下一跳信息,使用最长前缀匹配"""binary_ip = self._ip_to_binary(ip_address)node = self.rootlast_match = Nonefor bit in binary_ip:if bit in node.children:node = node.children[bit]if node.next_hop is not None:last_match = node.next_hopelse:breakreturn last_matchdef _ip_to_binary(self, ip):"""将 IP 地址转换为 32 位二进制字符串"""octets = ip.split('.')binary_ip = ''.join([format(int(octet), '08b') for octet in octets])return binary_ip# 测试代码
if __name__ == "__main__":routing_table = IPRoutingTrie()# 添加路由前缀routing_entries = [("192.168.0.0/16", "Router A"),("192.168.1.0/24", "Router B"),("10.0.0.0/8", "Router C"),("0.0.0.0/0", "Default Gateway"),]for prefix, next_hop in routing_entries:routing_table.insert(prefix, next_hop)# 查找目的 IP 地址的下一跳test_ips = ["192.168.1.100","192.168.2.50","10.1.2.3","8.8.8.8",]for ip in test_ips:next_hop = routing_table.search(ip)print(f"目的 IP {ip} 的下一跳为:{next_hop}")

结果

python">目的 IP 192.168.1.100 的下一跳为:Router B
目的 IP 192.168.2.50 的下一跳为:Router A
目的 IP 10.1.2.3 的下一跳为:Router C
目的 IP 8.8.8.8 的下一跳为:None

S3 应用3:基于 Trie 的压缩算法(LZW 算法)

python">class TrieNode:"""Trie 节点,用于 LZW 压缩算法"""def __init__(self, index=None):self.children = {}  # 子节点self.index = index  # 节点对应的词典索引def lzw_compress(uncompressed):"""使用 LZW 算法进行压缩,使用 Trie 优化"""# 初始化词典,包含所有单字符dict_size = 256root = TrieNode()for i in range(dict_size):root.children[chr(i)] = TrieNode(index=i)result = []node = rootw = ''for c in uncompressed:if c in node.children:node = node.children[c]w += celse:# 输出 w 的词典索引result.append(node.index)# 新的词典条目dict_size += 1node.children[c] = TrieNode(index=dict_size - 1)# 从根节点开始处理新字符node = root.children[c]w = c# 输出最后一个字符串的索引if w:result.append(node.index)return resultdef lzw_decompress(compressed):"""使用 LZW 算法进行解压"""# 初始化词典,包含所有单字符dict_size = 256dictionary = {i: chr(i) for i in range(dict_size)}result = []w = chr(compressed.pop(0))result.append(w)for k in compressed:if k in dictionary:entry = dictionary[k]elif k == dict_size:# 处理特殊情况entry = w + w[0]else:raise ValueError('Bad compressed k: %s' % k)result.append(entry)# 新的词典条目dictionary[dict_size] = w + entry[0]dict_size += 1w = entryreturn ''.join(result)# 测试代码
if __name__ == "__main__":data = "TOBEORNOTTOBEORTOBEORNOT" * 3print("原始数据:", data)compressed = lzw_compress(data)print("压缩结果:", compressed)decompressed = lzw_decompress(compressed.copy())print("解压结果:", decompressed)print("压缩比:", len(compressed) / len(data))

结果

python">原始数据: TOBEORNOTTOBEORTOBEORNOTTOBEORNOTTOBEORTOBEORNOTTOBEORNOTTOBEORTOBEORNOT
压缩结果: [84, 79, 66, 69, 79, 82, 78, 79, 84, 256, 258, 260, 265, 259, 261, 263, 268, 260, 262, 264, 257, 269, 272, 270, 275, 266, 279, 278, 278, 274]
解压结果: TOBEORNOTTOBEORTOBEORNOTTOBEORNOTTOBEORTOBEORNOTTOBEORNOTTOBEORTOBEORNOT
压缩比: 0.4166666666666667

http://www.ppmy.cn/ops/128194.html

相关文章

分布式哈希表有哪些?

分布式哈希表(Distributed Hash Table,DHT)是一种分布式系统,旨在让存储在其上的数据能够在整个网络中被有效地定位和访问。以下是对分布式哈希表的详细解析: 一、基本概念 定义:DHT是一种分布式的键值存…

Rust:何以内存安全

在编程语言的大家庭中,Rust 以其独特的内存安全特性脱颖而出,成为系统级编程和并发编程领域的明星语言。本文将深入探讨 Rust 的内存安全机制,包括所有权(Ownership)、借用检查(Borrow Checking&#xff09…

DataX简介及使用

目录 一、DataX离线同步工具DataX3.0介绍 1.1、 DataX 3.0概览 1.2、特征 1.3、DataX3.0框架设计 1.4、支持的数据元 1.5、DataX3.0核心架构 1.6、DataX 3.0六大核心优势 1.6.1、可靠的数据质量监控 1.6.2、丰富的数据转换功能 1.6.3、精准的速度控制 1.6.4、强劲的…

RHCE--nginx实现多IP访问多网站

思路: 一个主机可以提供多个IP还有多个网站,在nginx中配置多个sever模块 1.先挂载,查看配置文件 2.下载nginx,安装对应程序 3.关闭防火墙,设置seunix为0 4.创建多个IP地址,在一个网卡创建 方法1&#xf…

WPF+MVVM案例实战-自定义按钮实现(带图片文字虚线实线边框切换)

文章目录 [TOC](文章目录) 1、创建项目2、创建自定义控件类库3、实现自定义控件1. ImageTextButton 依赖属性实现2.样式模板实现 4、引用自定义控件库5、UI及功能实现1、 前端UI实现2、状态转换器 InverseBooleanConverter 实现3、MainViewModel.cs 实现 6、运行效果7、源代码获…

EureKa是什么?

Eureka 是一个源于 Netflix 公司的开源项目,主要用于实现服务注册和服务发现的功能。它是构建分布式系统中的微服务架构的一个关键组件。下面是对 Eureka 的解释: 基本概念 Eureka 是基于 REST 的服务,主要用于管理微服务架构中的服务实例的…

【SpringCloud】04-Gateway网关登录校验

1. 网关请求处理流程 2. 网关过滤器 3. 网关实现登录校验 Component // 参数构造器 RequiredArgsConstructor public class AuthGlobalFilter implements GlobalFilter, Ordered {private final AuthProperties authProperties;private final JwtTool jwtTool;private final A…

react-intl实现国际化多语言切换

react-intl 是一个用于在 React 应用中实现国际化(i18n)和本地化(l10n)的库。它提供了一组组件和 API,用于格式化消息、日期、时间、数字等。以下是如何使用 react-intl 实现国际化和多语言切换的详细步骤。 安装 rea…