看动画轻松理解「Trie树」

640?wx_fmt=gif

看动画轻松理解「Trie树」_第1张图片

作者 | 程序员小吴

责编 | 胡巍巍

 

扎心!“我学了半年 Python,还是找不到工作”

https://edu.csdn.net/topic/python115?utm_source=cxrs_bw

 

640?wx_fmt=png

Trie树

 

Trie这个名字取自“retrieval”,检索,因为Trie可以只用一个前缀便可以在一部字典中找到想要的单词。  

虽然发音与「Tree」一致,但为了将这种 字典树 与 普通二叉树 以示区别,程序员小吴一般读「Trie」尾部会重读一声,可以理解为读「TreeE」。

Trie树,也叫“字典树”。顾名思义,它是一个树形结构。它是一种专门处理字符串匹配的数据结构,用来解决在一组字符串集合中快速查找某个字符串的问题。

此外Trie树也称前缀树(因为某节点的后代存在共同的前缀,比如pan是panda的前缀)。

它的Key都为字符串,能做到高效查询和插入,时间复杂度为O(k),k为字符串长度,缺点是如果大量字符串没有共同前缀时很耗内存。

它的核心思想就是通过最大限度地减少无谓的字符串比较,使得查询高效率,即「用空间换时间」,再利用共同前缀来提高查询效率。

 

640?wx_fmt=png

Trie树的特点

 

假设有5个字符串,它们分别是:Code,Cook,Five,File,Fat。现在需要在里面多次查找某个字符串是否存在。如果每次查找,都是拿要查找的字符串跟这5个字符串依次进行字符串匹配,那效率就比较低,有没有更高效的方法呢?

如果将这5个字符串组织成下图的结构,从肉眼上扫描过去感官上是不是比查找起来会更加迅速。

看动画轻松理解「Trie树」_第2张图片

Trie树样子

通过上图,可以发现Trie树的三个特点:

  • 根节点不包含字符,除根节点外每一个节点都只包含一个字符。

  • 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。

  • 每个节点的所有子节点包含的字符都不相同。

通过动画理解Trie树构造的过程。在构造过程中的每一步,都相当于往Trie树中插入一个字符串。当所有字符串都插入完成之后,Trie树就构造好了。

看动画轻松理解「Trie树」_第3张图片

Trie 树构造

 

640?wx_fmt=png

Trie树的插入操作

 

看动画轻松理解「Trie树」_第4张图片

Trie树的插入操作

Trie树的插入操作很简单,其实就是将单词的每个字母逐一插入Trie树。插入前先看字母对应的节点是否存在,存在则共享该节点,不存在则创建对应的节点。比如要插入新单词Cook,就有下面几步:

  • 插入第一个字母c,发现Root节点下方存在子节点c,则共享节点c。

  • 插入第二个字母o,发现c节点下方存在子节点o,则共享节点o。

  • 插入第三个字母o,发现o节点下方不存在子节点o,则创建子节点o。

  • 插入第三个字母k,发现o节点下方不存在子节点k,则创建子节点k。

  • 至此,单词cook中所有字母已被插入Trie树中,然后设置节点k中的标志位,标记路径root->c->o->o->k这条路径上所有节点的字符可以组成一个单词Cook。

     

640?wx_fmt=png

Trie树的查询操作

 

在Trie树中查找一个字符串的时候,比如查找字符串code,可以将要查找的字符串分割成单个的字符c,o,d,e,然后从Trie树的根节点开始匹配。如图所示,绿色的路径就是在Trie树中匹配的路径。

看动画轻松理解「Trie树」_第5张图片

code的匹配路径

如果要查找的是字符串cod(鳕鱼)呢?还是可以用上面同样的方法,从根节点开始,沿着某条路径来匹配,如图所示,绿色的路径,是字符串cod匹配的路径。

但是,路径的最后一个节点「d」并不是橙色的,并不是单词标志位,所以cod字符串不存在。也就是说,cod是某个字符串的前缀子串,但并不能完全匹配任何字符串。

看动画轻松理解「Trie树」_第6张图片

cod的匹配路径

程序员不要当一条咸鱼,要向 cook 靠拢:)。

 

640?wx_fmt=png

Trie树的删除操作

 

Trie树的删除操作与二叉树的删除操作有类似的地方,需要考虑删除的节点所处的位置,这里分三种情况进行分析:

删除整个单词(比如hi

看动画轻松理解「Trie树」_第7张图片

删除整个单词

  • 从根节点开始查找第一个字符h。

  • 找到h子节点后,继续查找h的下一个子节点i。

  • i是单词hi的标志位,将该标志位去掉。

  • i节点是hi的叶子节点,将其删除。

  • 删除后发现h节点为叶子节点,并且不是单词标志位,也将其删除。

  • 这样就完成了hi单词的删除操作。

删除前缀单词(比如cod):

看动画轻松理解「Trie树」_第8张图片

删除前缀单词

这种方式删除比较简单。

只需要将cod单词整个字符串查找完后,d节点因为不是叶子节点,只需将其单词标志去掉即可。

删除分支单词(比如cook):

看动画轻松理解「Trie树」_第9张图片

删除分支单词

与 删除整个单词 情况类似,区别点在于删除到cook的第一个o时,该节点为非叶子节点,停止删除,这样就完成cook。

 

 

640?wx_fmt=png

Trie树的应用

 

事实上Trie树在日常生活中的使用随处可见,比如这个:

具体来说就是经常用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。

它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。

1. 前缀匹配

例如:找出一个字符串集合中所有以五分钟开头的字符串。我们只需要用所有字符串构造一个Trie树,然后输出以 五−>分−>钟 开头的路径上的关键字即可。

Trie树前缀匹配常用于搜索提示。如当输入一个网址,可以自动搜索出可能的选择。当没有完全匹配的搜索结果,可以返回前缀最相似的可能。

看动画轻松理解「Trie树」_第10张图片

Google搜索

2. 字符串检索

给出N个单词组成的熟词表,以及一篇全用小写英文书写的文章,按最早出现的顺序写出所有不在熟词表中的生词。

检索/查询功能是Trie树最原始的功能。给定一组字符串,查找某个字符串是否出现过,思路就是从根节点开始一个一个字符进行比较:

  • 如果沿路比较,发现不同的字符,则表示该字符串在集合中不存在。

  • 如果所有的字符全部比较完并且全部相同,还需判断最后一个节点的标志位(标记该节点是否代表一个关键字)。

 

当人工智能遇上生活 会擦出什么样的火花?

https://edu.csdn.net/topic/ai30?utm_source=csdn_bw

 

640?wx_fmt=png

Trie树的局限性

 

如前文所讲,Trie的核心思想是空间换时间,利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。

假设字符的种数有m个,有若干个长度为n的字符串构成了一个Trie树 ,则每个节点的出度为m(即每个节点的可能子节点数量为m),Trie树的高度为n。

很明显我们浪费了大量的空间来存储字符,此时Trie树的最坏空间复杂度为O(m^n)。

也正由于每个节点的出度为m,所以我们能够沿着树的一个个分支高效的向下逐个字符的查询,而不是遍历所有的字符串来查询,此时Trie树的最坏时间复杂度为O(n)。

这正是空间换时间的体现,也是利用公共前缀降低查询时间开销的体现。

作者简介:作者程序员小吴,哈工大学渣,目前正在学算法,开源项目 「 LeetCodeAnimation 」5500star,GitHub Trending 榜连续一月第一。欢迎大家关注我的微信公众号:五分钟学算法,一起学习,一起进步!

声明:本文为作者投稿,版权归其个人所有。

【End】

看动画轻松理解「Trie树」_第11张图片

 热 文 推 荐 

☞ “5G 将是一个彻底的失败通信技术” 

☞ 你还没听过 CynosDB 吗?不来这场数据库技术沙龙就要 OUT 了!

☞ 叫板苹果谷歌,微软将开发者应用分成上调至 95%

互联网没有春天

☞13 岁少女因几行 JS 代码被逮了!

云漫圈 | 如何给女朋友解释什么是HTTP

这份“插件英雄榜Top20”才是Chrome的正确打开方式!

剧情反转! 创始人去世事件再爆新料, 1.8亿美元难道去了天堂?

☞没有一个人,能躲过程序员的诱惑!

 

System.out.println("点个好看吧!");
console.log("点个好看吧!");
print("点个好看吧!");
printf("点个好看吧!\n");
cout << "点个好看吧!" << endl;
Console.WriteLine("点个好看吧!");
Response.Write("点个好看吧!");
alert("点个好看吧!")
echo "点个好看吧!"

640?wx_fmt=png喜欢就点击“好看”吧!

你可能感兴趣的:(看动画轻松理解「Trie树」)