Trie这个名字取自“retrieval”,检索,由于Trie能够只用一个前缀即可以在一部字典中找到想要的单词。
虽然发音与「Tree」一致,但为了将这种 字典树 与 普通二叉树 以示区别,程序员小吴通常读「Trie」尾部会重读一声,能够理解为读「TreeE」。程序员
Trie 树,也叫“字典树”。顾名思义,它是一个树形结构。它是一种专门处理字符串匹配的数据结构,用来解决在一组字符串集合中快速查找某个字符串的问题。数据结构
此外 Trie 树也称前缀树(由于某节点的后代存在共同的前缀,好比pan是panda的前缀)。动画
它的key都为字符串,能作到高效查询和插入,时间复杂度为O(k),k为字符串长度,缺点是若是大量字符串没有共同前缀时很耗内存。搜索引擎
它的核心思想就是经过最大限度地减小无谓的字符串比较,使得查询高效率,即「用空间换时间」,再利用共同前缀来提升查询效率。google
假设有 5 个字符串,它们分别是:code,cook,five,file,fat。如今须要在里面屡次查找某个字符串是否存在。若是每次查找,都是拿要查找的字符串跟这 5 个字符串依次进行字符串匹配,那效率就比较低,有没有更高效的方法呢?code
若是将这 5 个字符串组织成下图的结构,从肉眼上扫描过去感官上是否是比查找起来会更加迅速。cdn
经过上图,能够发现 Trie树 的三个特色:blog
经过动画理解 Trie 树构造的过程。在构造过程当中的每一步,都至关于往 Trie 树中插入一个字符串。当全部字符串都插入完成以后,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
在 Trie 树中查找一个字符串的时候,好比查找字符串 code
,能够将要查找的字符串分割成单个的字符 c,o,d,e,而后从 Trie 树的根节点开始匹配。如图所示,绿色的路径就是在 Trie 树中匹配的路径。
若是要查找的是字符串cod
(鳕鱼)呢?仍是能够用上面一样的方法,从根节点开始,沿着某条路径来匹配,如图所示,绿色的路径,是字符串cod
匹配的路径。可是,路径的最后一个节点「d」并非橙色的,并非单词标志位,因此cod
字符串不存在。也就是说,cod
是某个字符串的前缀子串,但并不能彻底匹配任何字符串。
程序员不要当一条咸鱼,要向
cook
靠拢:)
Trie树的删除操做与二叉树的删除操做有相似的地方,须要考虑删除的节点所处的位置,这里分三种状况进行分析:
hi
)h
h
子节点后,继续查找h
的下一个子节点i
i
是单词hi
的标志位,将该标志位去掉i
节点是hi
的叶子节点,将其删除h
节点为叶子节点,而且不是单词标志位,也将其删除hi
单词的删除操做cod
)cod
单词整个字符串查找完后,
d
节点由于不是叶子节点,只需将其单词标志去掉便可。
cook
)cook
的第一个
o
时,该节点为非叶子节点,中止删除,这样就完成
cook
字符串的删除操做。
事实上 Trie树 在平常生活中的使用随处可见,好比这个:
具体来讲就是常常用于统计和排序大量的字符串(但不只限于字符串),因此常常被搜索引擎系统用于文本词频统计。它的优势是:最大限度地减小无谓的字符串比较,查询效率比哈希表高。
例如:找出一个字符串集合中全部以 五分钟
开头的字符串。咱们只须要用全部字符串构造一个 trie树,而后输出以 五−>分−>钟 开头的路径上的关键字便可。
trie树前缀匹配经常使用于搜索提示。如当输入一个网址,能够自动搜索出可能的选择。当没有彻底匹配的搜索结果,能够返回前缀最类似的可能
给出 N 个单词组成的熟词表,以及一篇全用小写英文书写的文章,按最先出现的顺序写出全部不在熟词表中的生词。
检索/查询功能是Trie树最原始的功能。给定一组字符串,查找某个字符串是否出现过,思路就是从根节点开始一个一个字符进行比较:
如前文所讲,Trie的核心思想是空间换时间,利用字符串的公共前缀来下降查询时间的开销以达到提升效率的目的。
假设字符的种数有m
个,有若干个长度为n的字符串构成了一个 Trie树 ,则每一个节点的出度为 m
(即每一个节点的可能子节点数量为m
),Trie树 的高度为n
。很明显咱们浪费了大量的空间来存储字符,此时Trie树的最坏空间复杂度为O(m^n)
。也正因为每一个节点的出度为m
,因此咱们可以沿着树的一个个分支高效的向下逐个字符的查询,而不是遍历全部的字符串来查询,此时Trie树的最坏时间复杂度为O(n)
。
这正是空间换时间的体现,也是利用公共前缀下降查询时间开销的体现。