数据结构编程实践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.rhkb.cn/news/455623.html

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

相关文章

文件摆渡系统选型指南:如何找到最适合您的数据安全解决方案?

在当今数字化时代,数据的安全传输与共享已成为企业运营中不可或缺的一环。文件摆渡系统,作为实现数据在不同安全域之间高效、安全传输的重要工具,其选型直接关系到企业数据的安全性与业务效率。本文将为您详细介绍如何挑选最适合您企业的文件…

视频网站系统的设计与实现(论文+源码)_kaic

毕 业 设 计(论 文) 题目:视频网站系统 摘 要 使用旧方法对视频信息进行系统化管理已经不再让人们信赖了,把现在的网络信息技术运用在视频信息的管理上面可以解决许多信息管理上面的难题,比如处理数据时间很长&#…

为什么k8s不支持docker-kubernetes

为什么Kubernetes不再支持Docker? 在Kubernetes 1.20版本之后,Kubernetes宣布逐步停止对Docker作为容器运行时的支持。这一改变在容器管理领域引起了广泛关注。许多人不禁疑惑:Kubernetes与Docker一向密切合作,为何会做出这样的决…

骨传导耳机哪个品牌好?2024年五大热门精选骨传导耳机推荐

在当今快节奏的生活中,人们对于个人音频设备的需求不仅限于优质的音质体验,还越来越注重健康与舒适。骨传导耳机作为一种新兴的技术产品,以其独特的听觉传递方式——通过颞骨而非耳道传递声音——赢得了众多用户的青睐。这种技术不仅可以提供…

Webserver(1)Linux开发环境搭建

目录 配置软件虚拟机中安装ubuntu安装ubuntu18的操作系统 安装VM tools安装XshellVscode远程连接到虚拟机 配置软件 VMwareVScodeg安装ubuntu 18.04.iso 或者镜像版本 XShellXFTP 虚拟机中安装ubuntu 安装ubuntu18的操作系统 开启虚拟机 选择中文简体 安装VM tools 打开v…

炸了!改进Transformer!Transformer-BiGRU多变量回归预测(Matlab)

炸了!改进Transformer!Transformer-BiGRU多变量回归预测(Matlab) 目录 炸了!改进Transformer!Transformer-BiGRU多变量回归预测(Matlab)分类效果基本介绍程序设计参考资料 分类效果 …

python离线安装依赖

以pymsql依赖为例操作如下: Python Package Index(PyPI)的官方网址是: PyPI The Python Package Index 在这个网站上,你可以搜索、浏览和下载Python包。 tar -xvzf pymysql2-1.3.3.tar.gz cd pymysql2-1.3.3 python setup.p…

基于SSM机场网上订票系统的设计

管理员账户功能包括:系统首页,个人中心,用户管理,机票信息管理,订单信息管理,机场广告管理,系统管理 前台账号功能包括:系统首页,个人中心,机票信息&#xf…

CRMEB标准版Mysql修改sql_mode

数据库配置 1.宝塔控制面板-软件商店-MySql-设置 2.点击配置修改,查找sql-mode或sql_mode (可使用CtrlF快捷查找) 3.复制 NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION 然后替换粘贴,保存 注:MySQL8.0版本的 第三步用…

CRM在企业协同中发挥了哪些作用?

在当今快速变化的商业环境中,企业竞争力的核心已逐渐从单一的产品或服务优势,转向以客户为中心的综合能力构建。客户关系管理(CRM)系统作为这一转型的关键驱动力,正以前所未有的力度打破企业内部部门间的传统壁垒&…

MusePose模型部署指南

一、模型介绍 MusePose是一个基于扩散和姿势引导的虚拟人视频生成框架。 主要贡献可以概括如下: 发布的模型能够根据给定的姿势序列,生成参考图中人物的舞蹈视频,生成的结果质量超越了同一主题中几乎所有当前开源的模型。发布该 pose alig…

Python CGI编程-get、post-复选框、单选框、文本框、下拉列表

GET方法:将网址中的两个参数读取出来显示到浏览器中 url示例:表单示例:服务器脚本hello.py文件是放在/Library/WebServer/CGI-Executables,hello.py同样也需要通过chmod修改权限到755. 放在/Library/WebServer/Documents中的是get…

免费开源AI助手,颠覆你的数字生活体验

Apt Full作为一款开源且完全免费的软件,除了强大的自然语言处理能力,Apt Full还能够对图像和视频进行一系列复杂的AI增强处理,只需简单几步即可实现专业级的效果。 在图像处理方面,Apt Full提供了一套全面的AI工具,包…

关于写查询接口的一些理解

上篇文章我们讲了查看设备详细信息的接口。这篇文章我们来讲讲一般的查询接口怎么写。我们就以最简单的查询为例子,来讲讲怎么写查询接口。 这是写IT设备查询接口的要求: 首先要知道的是,你写任何接口都是针对某张表来进行操作的。就像这个接…

HCIP--1

同一区域内的OSPF路由器拥有一致的 LSDB, 在区域内,OSPF 采用 SPF算法计算路由一个区域太多路由器,硬件资源跟不上,所以多划分区域 OSPF 路由计算原理 1. 区域内路由计算 LSA 在OSPF中,每个路由器生成 LSA,用于告诉…

Bytebase 3.0.0 - AI 助手全面升级

🚀 新功能 SQL 编辑器里的 AI 助手:支持将自然语言转换成 SQL 语句,解释 SQL 代码,还能帮助发现潜在问题。 支持 SQL Server DML 语句一键回滚。支持 MariaDB 的在线大表变更。新的 SQL 审核规则: 要求为 MySQL 设置 …

我谈Sobel算子与高斯一阶微分的关系

现在算力提升了,最常用的一阶差分边缘检测算子已经不是Sobel算子了,而是高斯一阶微分。 高斯一阶微分 顾名思义,高斯函数的一阶导数。 Derivative of Gaussian 1D 一维直接扩展到二维。 禹晶、肖创柏、廖庆敏《数字图像处理(面…

vue-router3基本使用

vuex基本使用 vue2 对应的 vuex、vue-router 都为3. 项目创建与框架安装如下 vue create hellorouter3 npm install vue-router3 npm i vuex3 npm install npm run serve 处理新建About组件报错 根路径下创建.eslintrc.js文件,其内容如下: module.ex…

程序员:数字时代的先锋

随着科技的不断进步,程序员这一职业群体逐渐成为社会中不可或缺的一部分。他们以智慧和汗水为世界带来更多的便捷与创新。今天,我们将庆祝1024程序员节,这是一个向全球程序员们表达敬意和感激的节日。让我们一同走进程序员的内心世界&#xf…

Unity 实现音频(mp3)可控制进度条

目录 前言 一、拼UI 二、上代码 前言 效果如图:(因为是GIF格式,录不上音频) 一、拼UI 1.新建空物体添加AudioSource,给AudioSource添加音频文件,取消勾选PlayOnAwake,勾选上Loop 2.创建Slid…