一:什么是Trie树面试
Trie树,即字典树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计和排序大量的字符串(但不只限于字符串),因此常常被搜索引擎系统用于文本词频统计。它的优势是:最大限度地减小无谓的字符串比较,查询效率比哈希表高。
Trie的核心思想是空间换时间。利用字符串的公共前缀来下降查询时间的开销以达到提升效率的目的。算法
它有3个基本性质:
1. 根节点不包含字符,除根节点外每个节点都只包含一个字符。
2. 从根节点到某一节点,路径上通过的字符链接起来,为该节点对应的字符串。
3. 每一个节点的全部子节点包含的字符都不相同。数组
二:树的构建
举个在网上流传颇广的例子,以下:
题目:给你100000个长度不超过10的单词。对于每个单词,咱们要判断他出没出现过,若是出现了,求第一次出如今第几个位置。
分析:这题固然能够用hash来解决,可是本文重点介绍的是trie树,由于在某些方面它的用途更大。好比说对于某一个单词,咱们要询问它的前缀是否出现过。这样hash就很差搞了,而用trie仍是很简单。
如今回到例子中,若是咱们用最傻的方法,对于每个单词,咱们都要去查找它前面的单词中是否有它。那么这个算法的复杂度就是O(n2 )。显然对于100000的范围难以接受。如今咱们换个思路想。假设我要查询的单词是abcd,那么在他前面的单词中,以b,c,d,f之类开头的我显然没必要考虑。而只要找以a开头的中是否存在abcd就能够了。一样的,在以a开头中的单词中,咱们只要考虑以b做为第二个字母的,一次次缩小范围和提升针对性,这样一个树的模型就渐渐清晰了。
比如假设有b,abc,abd,bcd,abcd,efg,hii 这6个单词,咱们构建的树就是以下图这样的:
当时第一次看到这幅图的时候,便立马感到此树之不凡构造了。单单从上幅图即可窥知一二,比如大海搜人,立马就能肯定东南西北中的到底哪一个方位,如此迅速缩小查找的范围和提升查找的针对性,不失为一创举。
ok,如上图所示,对于每个节点,从根遍历到他的过程就是一个单词,若是这个节点被标记为红色,就表示这个单词存在,不然不存在。
那么,对于一个单词,我只要顺着他从根走到对应的节点,再看这个节点是否被标记为红色就能够知道它是否出现过了。把这个节点标记为红色,就至关于插入了这个单词。
这样一来咱们查询和插入能够一块儿完成,所用时间仅仅为单词长度,在这一个样例,即是10。
咱们能够看到,trie树每一层的节点数是26i 级别的。因此为了节省空间。咱们用动态链表,或者用数组来模拟动态。空间的花费,不会超过单词数×单词长度。数据结构
三:前缀查询
已知n个由小写字母构成的平均长度为10的单词,判断其中是否存在某个串为另外一个串的前缀子串。下面对比3种方法:
最容易想到的:即从字符串集中从头日后搜,看每一个字符串是否为字符串集中某个字符串的前缀,复杂度为O(n2)。
使用hash:咱们用hash存下全部字符串的全部的前缀子串,创建存有子串hash的复杂度为O(n*len)
,而查询的复杂度为O(n)* O(1)= O(n)
。
使用trie:由于当查询如字符串abc是否为某个字符串的前缀时,显然以b,c,d....等不是以a开头的字符串就不用查找了。因此创建trie的复杂度为O(n*len)
,而创建+查询在trie中是能够同时执行的,创建的过程也就能够成为查询的过程,hash就不能实现这个功能。因此总的复杂度为O(n*len)
,实际查询的复杂度也只是O(len)
。(说白了,就是Trie树的平均高度h为len,因此Trie树的查询复杂度为O(h)=O(len)
。比如一棵二叉平衡树的高度为logN,则其查询,插入的平均时间复杂度亦为O(logN)
)。搜索引擎
四:查询
Trie树是简单但实用的数据结构,一般用于实现字典查询。咱们作即时响应用户输入的AJAX搜索框时,就是Trie开始。本质上,Trie是一颗存储多个字符串的树。相邻节点间的边表明一个字符,这样树的每条分支表明一则子串,而树的叶节点则表明完整的字符串。和普通树不一样的地方是,相同的字符串前缀共享同一条分支。下面,再举一个例子。给出一组单词,inn, int, at, age, adv, ant, 咱们能够获得下面的Trie:
spa
能够看出:
每条边对应一个字母。
每一个节点对应一项前缀。叶节点对应最长前缀,即单词自己。
单词inn与单词int有共同的前缀“in”, 所以他们共享左边的一条分支,root->i->in。同理,ate, age, adv, 和ant共享前缀"a",因此他们共享从根节点到节点"a"的边。.net
查询操纵很是简单。好比要查找int,顺着路径i -> in -> int就找到了。code
搭建Trie的基本算法也很简单,无非是逐一把每则单词的每一个字母插入Trie。插入前先看前缀是否存在。若是存在,就共享,不然建立对应的节点和边。好比要插入单词add,就有下面几步:
1. 考察前缀"a",发现边a已经存在。因而顺着边a走到节点a。
2. 考察剩下的字符串"dd"的前缀"d",发现从节点a出发,已经有边d存在。因而顺着边d走到节点ad
3. 考察最后一个字符"d",这下从节点ad出发没有边d了,因而建立节点ad的子节点add,并把边ad->add标记为d。blog
五:Trie树的应用
第一:词频统计;第二: 前缀匹配;第三:去重排序
适用范围:数据量大,重复多,可是数据种类小能够放入内存
基本原理及要点:实现方式,节点孩子的表示方式
扩展:压缩实现。
面试题有:有一个1G大小的一个文件,里面每一行是一个词,词的大小不超过16字节,内存限制大小是1M。返回频数最高的100个词。