以前说搜索提示的时候留了一个尾巴,就是Trie树的结构没有说,这一篇简单的说一下Trie树的实现方式。golang
在计算机科学中,trie,又称前缀树或字典树,是一种有序树,用于保存关联数组,其中的键一般是字符串。与二叉查找树不一样,键不是直接保存在节点中,而是由节点在树中的位置决定。一个节点的全部子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串。通常状况下,不是全部的节点都有对应的值,只有叶子节点和部份内部节点所对应的键才有相关的值。算法
Trie这个术语来自于retrieval。根据词源学,trie的发明者Edward Fredkin把它读做英语发音:/ˈtriː/ "tree"。可是,其余做者把它读做英语发音:/ˈtraɪ/ "try"编程
以上文字来自维基百科
数组
搜索提示和分词是Trie树的经典应用范围。微信
Trie树
又叫字典树
,本质上是一个多叉树,每个节点就是一个多叉的结构,若是是英文的匹配,那么是一个26叉树
,每一个节点一个26长度的数组,每一个节点的数据结构以下数据结构
type TrieNode struct{ flag bool hasNext bool nexts [26]*TrieNode }
其中flag
保存的是当前节点是否已是一个完整字符串了。hasNext
表示是否还有下一节点,而Trie树
画出来就是下面这个样子。

从画出来的图,很直观的能够看出来这棵树的构造方法和遍历方法,由于每一个节点都有一个26长度的数组,来了一个字符,经过字符的编号直接就能够遍历到下一个节点,查找的时候复杂度就是O(K),K表示查找的字符串长度,这种数据结构简单明了,实现起来也很容易。优化
可是这种数据结构有个问题,就是内存使用得太多了,若是是中文查找的话,须要把全部的中国字都编号到这个数组中,因而有一种优化方法,就是把数组变成变长的,若是按照二分进行每一个节点的查找的话,每一个节点的查找时间变成了O(lg(n)),总体的查找时间变成K*O(log(n)),n是数组平均长度,这样的话,虽然内存的使用上下降了,可是查找速度变长了,并且插入的时候须要按序插入,插入也变慢了。spa
按照真正的树形结构,经过各类指针满天飞的形式来实现Trie树,基本上属于新人练手的水平,纯粹为了了解这个数据结构或者大学生作作课程设计,工程化的可能性几乎为0。设计
正由于有上面的各类问题,因而有人发明了双数组Trie树
,其实在这个以前还有个三数组的Trie树
,为了避免让你们更迷糊,咱们直接来看看双数组的形式吧。指针
所谓双数组Trie树,固然就是经过两个数组来实现这棵树了,这两个数组分别叫base数组
和check数组
,一个是基础数组,一个是检查数组,Suggestion服务对Trie树的操做基本上须要有Trie树的构建部分和Trie树的查询部分,插入操做基本上不多使用,后面说数据部分的时候会说到为何。
Trie树其实是一种有限状态机
,经过状态转移矩阵
在各个状态之间跳转,咱们不去管这两个概念,会越说越糊涂,若是你实在是有兴趣,能够本身去查查资料,咱们直接来点实在的,用一个例子来看看双数组Trie树
是如何工做的。
假设咱们的词库里面有这么一些词,中国,北京,中华,华北
这几个词语,须要构建一棵Trie树
,构建好了之后,用户输入中
字的时候,能把中国,中华
都给提示出来。这里的例子直接使用了中文是为了更直观,免得都是abc这种例子容易乱,虽然最后的处理上中文有点特殊,但不影响算法描述。
首先,拿到这些个词之后,第一步就要构建这棵Trie树了,构建以前,咱们先对每个字进行编号一下,这个只是为了方便描述,中:1,国:2,北:3,京:4,华:5
。
初始化空数组,初始化base和check两个空数组。
base的元素为1表示是开始位偏移为1,为-1表示没有数据。
check的元素中,-1表示没有数据,-2表示这是一个词结束了,-3表示是根节点,以下图所示

初始化好了之后就能够开始依次读取数据了,先读取出中国
两个字,开始插入
中国
这个词中
字从根节点开始,base[0]=1,插入位置为base[0]+中
,中
的编号为1,因此是base[0]+1=1+1=2
,因而获得应该插入到base[2]的位置。
检查check[2]
,看到check[2]为-1,表示base[2]没有数据,能够插入
更新check[2]
为上一个节点的base数组的下标,上一个节点是base[0],因此更新为0
更新base[2]
为上一个节点的base数组的值,上一个节点base[0]的值是1,因此更新为1
按照上面的四个步骤更新完了之后,整个数组变成下面这个样子,对着图能够仔细想一想整个过程,而后你想一想伪代码是什么样子的,再想一想如何编程。

国
字因为这个词语还没结束,因此从base[2]开始继续往下走
从base[2]开始,插入位置为base[2]+国
,国
的编号是2,因此是base[2]+2=1+2=3
,应该插入到base[3]中
检查check[3]
,看到check[2]为-1,表示base[2]没有数据,能够插入
更新check[3]
为上一个节点的base数组下标,上一个是base[2],因此更新为2
更新base[3]
为上一个节点的base数组的值,上一个节点base[2]的值是2,因此更新为2

北京
和中华
这个词你们能够本身插入一下北京
和中华
这两个词,插入之后结构变成下面的样子

华北
这个词到如今一切都很顺利,接下来插入华北
这个词,这里就会遇到冲突了,遇到冲突之后,进行以下算法
华
字这个插入正常,最后图像以下所示

北
字因为这个词语还没结束,因此从base[6]开始继续往下走
从base[6]开始,插入位置为base[2]+北
,国
的编号是3,因此是base[6]+3=1+3=4
,应该插入到base[4]中
检查check[4]
,看到check[2]为0,表示base[4]有数据,产生冲突!!
从base[6]日后找空闲空闲,空闲空间的概念是两个数组都是-1
找到base[8]和check[8]为空闲空间
更新check[8]为上一节点的值,6
计算base[6]应该的值为8-北=8-3=5,更新base[6]为5,解决冲突!!

要进行查询,就相对简单了,拿到一个词,好比中华
,先肯定每一个字的编号,而后按照两个数组去遍历,就知道这个词在不在这个Trie树中了,复杂度就是词的长度。
本篇比较简单,就是上次搜索提示的一个小尾巴,后面会说到分词的技术,这个Trie树也是分词的主要数据结构。
若是你以为不错,欢迎转发给更多人看到,也欢迎关注个人公众号,主要聊聊搜索,推荐,广告技术,还有瞎扯。。文章会在这里首先发出来:)扫描或者搜索微信号XJJ267或者搜索西加加语言就行