数据结构编程实践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工业路由器最新案例:高原气象站网络升级项目

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

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

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

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

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

1024-过去一年的总结

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

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

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

【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…

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

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

学习文档(5)

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

三周精通FastAPI:5 查询参数和字符串校验

FastAPI手册:https://fastapi.tiangolo.com/zh/tutorial/query-params-str-validations/ 查询参数和字符串校验 FastAPI 允许你为参数声明额外的信息和校验。让我们以下面的应用程序为例: from fastapi import FastAPIapp FastAPI()app.get("/it…

基于springboot+thymeleaf+springsecurity搭建一套web小案例

一、前言 本案例中的源代码已上传到资源库,可自行下载,传送阵 https://download.csdn.net/download/qq_36260963/89906196 Spring Boot是为了简化Spring应用的创建、运行、调试、部署等而出现的,使用它可以做到专注于Spring应用的开发&#x…

git clone 鉴权失败

git clone 鉴权失败问题 1. 问题描述2. 解决方法 1. 问题描述 使用git clone自己的代码报如下错误: 正克隆到 xxx... Username for https://github.com: Password for https://xxxgithub.com: remote: Support for password authentication was removed on Augu…

RAG流程的实现与改进

一、 RAG流程图 数据入库:读取本地数据并切成小块,并把这些小块经过编码embedding后,存储在一个向量数据库中(下图1——6步);相关性检索:用户提出问题,问题经过编码,再在…

Vue项目中实现拖拽上传附件:原生JS与Element UI组件方法对比

在现代化的Web应用中,文件上传是一个基本功能。随着技术的发展,拖拽上传已经成为提升用户体验的一个重要特性。在Vue项目中,我们可以通过原生JavaScript或使用Element UI组件来实现这一功能。下面我们将分别介绍这两种方法,并对比…

吴恩达深度学习笔记(6)

正交化 为了提高算法准确率,我们想到的方法 收集更多的训练数据增强样本多样性使用梯度下降将算法使算法训练时间更长换一种优化算法更复杂或者更简单的神经网络利用dropout 或者L2正则化改变网络框架更换激活函数改变隐藏单元个数 为了使有监督机制的学习系统良…

vue使用jquery的ajax,页面跳转

一、引入jquery依赖 打开终端更新npm npm install -g npm 更新完后引入输入npm install jquery 加载完后 在最外层的package.json文件中加入以下代码 配置好后导入jquery 设置变量用于接收服务器传输的数据 定义ajax申请数据 服务器的Controller层传输数据 (…

【VUE小型网站开发】初始环境搭建

1. 初始化VUE项目 1.1 创建vue项目 1.2 删除多余的界面 根据自己情况删除红框内的文件 清理app页面代码 1.3 引入vue-router 1.3.1 下载vue-router npm install vue-router1.3.2 配置vue-router 在 main.js 或 main.ts 中引入 vue-router import ./assets/main.css im…

Android 图片相识度比较(pHash)

概述 在 Android 中,要比对两张 Bitmap 图片的相似度,常见的方法有基于像素差异、直方图比较、或者使用一些更高级的算法如 SSIM(结构相似性)和感知哈希(pHash)。 1. 基于像素的差异比较 可以逐像素比较…

基于MATLAB车牌识别系统设计

MATLAB车牌识别系统设计 实践目的 车牌是一辆汽车独一无二的信息,因此,对车辆牌照的识别技术可以作为 辨识一辆车最为有效的方法。随着ITS(智能交通系统)的高速发展,对车牌识别技术的研究也随之发展。从根本上讲,牌照识别应用了…

中缀表达式转后缀表达式(逆波兰表达式)及如何计算后缀表达式

目录 中缀、后缀表达式简介 中缀转后缀的规则 模拟中缀转后缀 中缀转后缀代码 后缀表达式求值 后缀表达式求值代码 Leetcode相关题目 中缀、后缀表达式简介 首先说说什么是中缀表达式,中缀表达式中,操作符是以中缀形式处于操作数的中间。例如&…