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

本文目录

    • 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 示例

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}")

结果

单词 '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

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("未找到匹配的建议。")

结果

正在构建 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 路由中的最长前缀匹配

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}")

结果

目的 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 算法)

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))

结果

原始数据: 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.mzph.cn/news/882554.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

THP4 SOP16 芯片 高速光耦芯片

光电耦合器输入端加电信号使发光源发光,光的强度取决于激励电流的大小,此光照射到封装在一起的受光器上后,因光电效应而产生了光电流,由受光器输出端引出,这样就实现了电一光一电的转换。 由于光耦合器输入输出间互相…

防火墙和堡垒机有什么区别?

防火墙和堡垒机在网络安全领域都扮演着至关重要的角色,但它们在功能、部署位置、作用范围等方面存在显著差异。 一、功能定位 防火墙:防火墙是一种网络安全系统,主要作用是在私有网络与公网之间建立一道安全屏障,监控和控制网络流量。它通过预定义的安全规则,决定哪些数…

5g工业路由器最新案例:高原气象站网络升级项目

背景: 某省气象局决定在高原地区升级其气象观测网络,以提高天气预报的准确性和及时性,同时为气候变化研究提供更可靠的数据支持。该项目面临以下挑战: 需要在高原广袤且地形复杂的区域部署大量自动气象站,要求网络覆…

【Excel】【VB和JS】表格内容姓名、卡号、身份证敏感信息转换为图片打印

VB代码: Function ConvertCellToImageAndPlace(n As Long, m As Long)Dim sourceCell As RangeSet sourceCell Sheets("Sheet2").Cells(n, m)sourceCell.CopyPicture Appearance:xlScreen, Format:xlPictureDim targetCell As RangeSet targetCell She…

优化SQL查询的最佳实践:提升数据库性能的关键

SQL 查询是数据库操作的核心,特别是当数据量庞大时,性能问题尤为明显。优化 SQL 查询不仅能减少响应时间,还能提高系统整体的可伸缩性。本文将从索引、查询结构、数据库设计和缓存等方面详细介绍如何优化 SQL 查询以提升性能。 一、索引的使…

kotlin 中 ::class ::class.java 和 .javaClass 区别

在 Kotlin 中,获取一个类的类对象(Class 对象)有几种不同的方式,包括 ::class、::class.java 和 .javaClass。每种方式都有其特定的用途和适用场景。下面是对这三种方式的详细解释: ::class 用途:获取 Kotl…

Java配置 Redis 连接互斥锁或队列预先加载缓存

学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……) 2、学会Oracle数据库入门到入土用法(创作中……) 3、手把手教你开发炫酷的vbs脚本制作(完善中……) 4、牛逼哄哄的 IDEA编程利器技巧(编写中……) 5、面经吐血整理的 面试技…

【HuggingFace 如何上传数据集 (2) 】国内网络-稳定上传图片、文本等各种格式的数据

【HuggingFace 下载】diffusers 中的特定模型下载,access token 使用方法总结【HuggingFace 下载中断】Git LFS 如何下载指定文件、单个文件夹?【HuggingFace 如何上传数据集】快速上传图片、文本等各种格式的数据 上文的方法因为是 https 协议&#xf…

CIM系统:智慧城市的数字基石

计算机集成制造系统(CIM)是智慧城市建设中的关键技术,它通过集成多种信息技术,为城市提供一个全面的数字化镜像。CIM系统不仅涉及建筑信息模型(BIM)、地理信息系统(GIS)、物联网&…

1024-过去一年的总结

目录 前言 (10月-24.01)故事的开始 (2月-7月)漫长的学习与探索 (8月-现在)低谷期 自我审视 前言 1024在即,就借此机会讲一讲过去一年的经历,也算是全方位总结一下自己 (10月-24.01)故事的开始 那就从去年大一入学…

前端优化:从Vue/React/Svelte的数组更新->渲染策略剖析数组大列表数据展示优化策略

在现代前端框架中,数组的渲染是一个重要的功能。不同的框架在处理数组的操作(如新增、删除和更新)时有不同的实现方式和优化手段。本文将对比 Vue、React 和 Svelte 在数组渲染方面的特点,并讨论其优缺点,特别是与直接…

【C++】STL——priority_queue优先级队列

目录 前言priority_queue的使用简单使用在OJ中的使用 priority_queue的模拟实现基本功能仿函数在这里插入图片描述 前言 上一节我们说了stack和queue这两种容器适配器,而priority_queue(优先级队列)同样也是属于容器适配器,它会优…

使用Python在Jupyter Notebook中显示Markdown文本

使用Python在Jupyter Notebook中显示Markdown文本 引言1. 导入必要的模块2. 定义一个函数来显示Markdown文本3. 使用print_md函数显示Markdown文本4. 总结 引言 作为一名Python初级程序员,你可能已经熟悉了Jupyter Notebook这个强大的工具。Jupyter Notebook不仅支…

【Fargo】9:模拟图片采集的内存泄漏std::bad_alloc

std::bad_alloc 崩溃。这样的内存分配会导致内存耗尽 is simulating an image of size 640x480 with 3 bytes per pixel, resulting in an allocation of approximately 921,600 bytes (or around 900 KB) for each image. The error you’re encountering (std::bad_alloc) ty…

Spring Boot中使用FlexyPool动态监控管理数据库连接池

在现代软件开发中,数据库连接管理是性能优化的关键一环。传统的固定大小连接池在面对突发流量或持续增长的数据访问需求时,往往显得力不从心。FlexyPool正是为了解决这一问题而诞生的创新解决方案。它不仅能够根据实际需求动态调整连接池大小&#xff0c…

【c++】左值右值

1. 普通类型的变量,因为有名字,可以取地址,都认为是左值。 2. const修饰的常量,不可修改,只读类型的,理论应该按照右值对待,但因为其可以取地址(如果只是const类型常量的定义,编译器…

Arduino 1.8 无法启动,闪屏过后,不出主界面

突然打不开了,闪屏过后,不出主界面,一直这样,任务栏有图标。 1. 使用调试版的arduino_debug.exe运行显示如下,也没有任何错误。 C:\Users\youuser>"C:\Program Files (x86)\Arduino\arduino_debug.exe"…

Java 多线程(四)—— 线程安全 与 volatile 与 单例模式

什么是线程安全 在进行多线程编程的时候,当我们编写出来的多线程的代码运行结果不符合我们的预期的时候,这时候就是 bug,这种 bug 是由于多线程的问题而产生出来的 bug 我们称之为 线程安全问题 当我们编写出来的多线程代码运行之后的结果符…

计算机毕业设计选题推荐【基础功能+创新点】【Java方向】

选以下是30个Java方向的精选毕设选题,每个选题包括基础功能和创新点,供你参考。 文末有作者名片喔,需要交流 (毕业) 可联系 在线教育平台 基础功能:用户注册与登录,课程浏览与搜索,课程购买与观看&#xf…

学习文档(5)

Redis应用 目录 Redis应用 Redis 除了做缓存,还能做什么? Redis 可以做消息队列么? Redis 可以做搜索引擎么? 如何基于 Redis 实现延时任务? Redis 除了做缓存,还能做什么? 分布式锁&…