[数据结构学习笔记11] 前序树(Trie/Prefix tree)

news/2025/1/11 10:14:52/文章来源:https://www.cnblogs.com/Eagle6970/p/18662836

前序树(Trie/Prefix tree),它的一个典型的应用场景在搜索引擎里,当你输入查询关键字的时候,会联想自动补齐你想要输入的内容。比如,你输入app,下面可能会出来联想Apple, Applied等等。

什么是Trie?

Trie(读作Try)是这样一个数据结构,它把短语或者单词分解字母,然后以一种方式去存储,让添加、删除、查找或者自动补齐短语/单词更高效。

我们看一个例子:

1. 开始我们有一个空的根节点

                       *

2. 我们想把apple存放进来

2.1                  *

                 a

             p

           p

         l

      e (The end of our phase or word!)

3. 把cat,dog加进来

3.1                *

              a       c       d

            p         a       o

           p          t         g 

           l

         e

4. 把duck加进来,d作为了dog和duck公共的前序字母

4.1                 *

              a       c       d

            p         a       o     u

           p                       c

           l                                k

         e

5. 把dune和monk,monkey加进来

5.1                  *

              a       c       d              m

            p         a       o     u             o

           p                 g       c    n         n

           l                                k    e          k

         e                                                      e

                                                                   y

上面的前序树里,我们有apple,app,cat,dog,duck,dune,monkey,monk。

查找单词:

1. 我们要查找eagle,首先我们把eagle拆解成e,a,g,l,e,我们看e是不是在根节点的孩子节点里。这里我们并没有e,所以我们可以停止搜索,告知eagle不在我们树里。

2. 我们要查找monk,过程是一样的,把monk拆解成m,o,n,k,看m在不在孩子节点,这里m是其中一个孩子节点,所以继续往下找o,同样也能找到,可以看到,我们这里查找的时候就关注在m,o这条分支上,继续往下,n,k可以都找到。

2.1 如果我们是要看完整的单词是否存在,那我们要看最后一个字母是不是单词的结尾,monk的例子上,我们看到k是被标记为结尾的,所以monk是一个完整的单词。

2.2 如果我们是看prefix是否存在,那么我们不需要看最后一个字母是不是单词结尾,monk是存在的,同样的m,mo,mon也都是存在的。

删除单词:

如果我们要删除单词,我们不能简单的把这个单词的字母删掉,因为这个单词的字母有可能是和其他单词共用了前缀。

1. 查找要删除的单词在我们前序树里存在

2. 如果单词的某个字母是和其他单词共享的,那不能删除。比如要删除duck,d是和dog,dune共享的,u是和dune共享的

3. 我们要删除的单词不是另一个单词的子集

所以我们这样删:

把我们要删除的单词解散成字母,然后反相删除。从最后一个字母开始,看这个字母是否和其他单词共享,或者是其他单词的一部分,如果都不是,则把这个字母删除,然后相同的方法,查看前一个字母,直到字母和其他单词共享则停止。

我们删除duck后的前序树是这个样子:d,u还在,因为和其他单词共用,c,k被删除了。

                        *

              a       c       d              m

            p         a       o     u             o

           p          t         g            n         n

           l                                     e          k

         e                                                      e

                                                                   y

前序树应用场景:

前序树应用,直接的例子,对于我们上面的前序树,加入你输入d,它会快速返回给你dog,duck,dune作为你可能要查找的目标。

1. 自动补齐或者预测文字:比如在搜索引擎,message app,email client,我们常会看到自动提示,或者自动补齐的功能;

2. 拼写检查和矫正:前序树可以用于存储字典,从而帮助查找和建议正确的单词;当一个不正确的字母输入时,我们可以回退几步,给出可能正确的单词选项;

3. IP路由和网络路由表:前序树中每个节点表示ip的一部分,子节点表示剩下部分可能的值,遍历前序树,路由器可以查看下一个hop;

4. 单词游戏:Wordle。

 

代码实现(javascript)

 我们将要实现一下功能:

1. 插入一个单词

2. 查找一个单词是否存在

3. 检查是否有单词能匹配输入前缀

4. 返回所有匹配输入前缀的单词

class TrieNode {constructor() {// Each TrieNode has a map of children nodes,// where the key is the character and the value is the child TrieNodethis.children = new Map();// Flag to indicate if the current TrieNode represents the end of a wordthis.isEndOfWord = false;}  
}class Trie {constructor() {// The root of the Trie is an empty TrieNodethis.root = new TrieNode();}  insert(word) {let current = this.root;for (let i = 0; i < word.length; i++) {const char = word[i];// if the character doesn't exist as a child node,// create a new TrieNode for itif (!current.children.get(char)) {current.children.set(char, new TrieNode());}// Move to the next TrieNodeecurrent = current.children.get(char);}// Mark the end of the word by setting isEndOfWord to truecurrent.isEndOfWord = true; }  search(word) {let current = this.root;for (let i = 0; i < word.length; i++) {const char = word[i];// if the character doesn't exist as a child node,// the word doesn't exist in the Trieif (!current.children.get(char)) {return false;}// move to the next TrieNodecurrent = current.children.get(char);}return current.isEndOfWord;}startsWith(prefix) {let current = this.root;for (let i = 0; i < prefix.length; i++) {const char = prefix[i];if (!current.children.get(char)) {return false;}currrent = current.children.get(char);}return true;}getAllWords(prefix = '') {const words = [];// Find the node corresponding to the given prefixconst current = this.#findNode(prefix);if (current) {// if the node exists, traverse the Trie starting from that node // to find all words and add them to the 'words' arraythis.#traverse(current, prefix, words);}return words;}delete(word) {let current = this.root;const stack = [];let index = 0;// Find the last node of the word in the Triewhile (index < word.length) {const char = word[index];if (!current.children.get(char)) {return;}stack.push({ node: current, char });current = current.children.get(char);index++;}if (!current.isEndOfWord) {// word doesn't exist in the Trie, nothing to deletereturn;}// Mark the last node as not representing the end of a wordcurrent.isEndOfWord = false;// Remove nodes in reverse order until reaching a node // that has other children or is the end of another wordwhile (stack.length > 0) {const { node, char } = stack.pop();if (current.children.size === 0 && !current.isEndOfWord) {node.children.delete(char);current = node;} else { break; }}}#findNode(prefix) {let current = this.root;for (let i = 0; i < prefix.length; i++) {const char = prefix[i];if (!current.children.get(char)) {return null;}current = current.children.get(char);}return current;}#traverse(node, prefix, words) {const stack = [];stack.push ({ node, prefix });while (stack.length > 0) {const { node, prefix } = stack.pop();// If the current node represents the end of a word,// add the word to the 'words' arrayif (node.isEndOfWord) {words.push(prefix);} // Push all child nodes to the stack to continue traversalfor (const char of node.children.keys()) {const childNode = node.children.get(char);stack.push({ node: childNode, prefix: prefix + char });}}}
}

使用Trie

cnost trie = new Trie();
trie.insert("apple");
trie.insert("app");
trie.insert("monkey");
trie.insert("monk");
trie.insert("cat");
trie.insert("dog");
trie.insert("duck");
trie.insert("dune");trie.search("apple"); // true
tire.search("bat");  // false

trie.getAllWords("ap"); // ['apple', 'app']
trie.getAllWords("b"); // []

trie.delete("monkey");
trie.getAllWords("m") // ['monk']

 

性能

前序树,我们底层使用了一个哈希表来实现的。性能来自两个因素:

1. 输入的单词或者前缀的长度

2. 给定一个字母,它下面所包含的子节点的数量

我们插入,查找,删除都是线性时间复杂度O(k),k是单词长度。我们底层是hashmap,一般检查一个字母是否存在是O(1)复杂度,最坏是O(n),这个发生在一个节点它有异常多的子节点。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/867645.html

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

相关文章

信息安全数学基础-期末(第八章)

群 定义 半群的定义:设S是一个具有结合法的非空集合.如果S中有一个元素e;使得对S中所有元素a,都有 ea=ae=a. 单位元的定义: 性质:设 S是一个有单位元的半群, 则对 S 中的任意可逆元 a, 其逆元 a 是唯的 群的定义: 子群 定义: 同态和同构 定义: 单射、满射、双射: 单射确…

Python/Conda环境配置

Python/conda环境配置 需用: Anaconda Pycharm 均在:U23\00公共空间\软件安装包\Python 步骤 1.安装Anaconda (最好安装在英文路径下,避免不必要的问题) 注意:一定要勾选红框选项!2.打开命令窗 开始--Anaconda—Anaconda Prompt (Anaconda) 初始环境为--base 3.创建环…

Mac电脑必备的菜单栏管理软件 Bartender 5

Mac电脑必备的菜单栏管理软件 Bartender 5 介绍 Bartender 5,是一款菜单栏管理软件,可以帮助用户隐藏、组织和自定义Mac菜单栏中的图标和通知。使用Bartender 5,用户可以将不常用的图标隐藏起来,使菜单栏保持整洁,并只显示重要的通知和信息。此外,Bartender 5还支持自定义…

2024年总结及2025年目标之关键字【稳进】

2024年总结及2025年目标之关键字【稳进】1. 感受 时光荏苒,都731天(2年时间)下来了,从第一年的【坚持】,到第二年的【提速】,定目标,现在回头看,还是那句话【事非经过不知难】,那又怎么样呢,再难不是也过来了吗:D,接下来就是【而今迈步从头越】!读书时间大增,尤其…

现货黄金

可能WXY反弹 短期见顶了 2695-2700阻力 支撑2665-2670

深入解析 Spring AI 系列:以OpenAI与Moonshot案例为例寻找共同点

今天,我们将重点探讨对接的业务逻辑。为了帮助大家更直观地掌握其中的规律性,我将通过对比OpenAI与《月之暗面》中的Moonshot两个案例来阐述这一点。通过这样的对比,大家可以更清晰地看到,这些对接业务的整体框架其实非常相似。换句话说,我们要做的工作只是其中的一小部分…

硬盘检测工具|数据恢复

硬盘检测工具设置 # 在settings中开启如下配置,而后关闭数据恢复

VMware ESXi 8.0U3c macOS Unlocker OEM BIOS Huawei (华为) 定制版

VMware ESXi 8.0U3c macOS Unlocker & OEM BIOS Huawei (华为) 定制版VMware ESXi 8.0U3c macOS Unlocker & OEM BIOS Huawei (华为) 定制版 ESXi 8.0U3c 标准版,Dell (戴尔)、HPE (慧与)、Lenovo (联想)、Inspur (浪潮)、Cisco (思科)、Hitachi (日立)、Fujitsu (富士…

【Go编程】流程控制

一、流程控制的作用: 流程控制语句是用来控制程序中各语句执行顺序的语句,可以把语句组合成能完成一定功能的小逻辑模块。 二、控制语句的分类: 控制语句分为三类:顺序、选择和循环。 “顺序结构”代表“先执行a,再执行b”的逻辑。 “条件判断结构”代表“如果…,则…”的…

在CDN上搭建支持反向代理的C2服务器(下)

免责声明: 本文技术只做研究之用,禁止用来从事非法用途,如有使用文章中的技术从事非法活动,一切后果由使用者自负,与作者无关。一、摘要 在上一篇文章中, 完成了Microsoft Azure 上的环境准备工作, 已经成功安装并配置了用于 Nginx 反向代理的虚拟机(VM)和用于 Cobalt Strike…

在CDN上搭建支持反向代理的C2服务器(上)

免责声明: 本文技术只做研究之用,禁止用来从事非法用途,如有使用文章中的技术从事非法活动,一切后果由使用者自负,与作者无关。一、摘要 在本文中,将探讨在微软Azure的CDN网络中使用C2域名和Nginx作为反向代理来构建一个红队基础设施。内容包括:C2域的选择和DNS配置、Cobalt St…

换个 AI 写

被遗忘,无法参与,无法长时间停课,对学校或教练组心存疑虑。每月的考试成绩。为了奥运会,并花费了无法停课的时间,以换取比赛课程的月度考试成绩。他们可以通过宣传来间接提高成绩。在未来的职业发展中,她取得了优异的成绩。缺乏时间,对江平在奥运会上取得优异成绩的怀疑…