1. 字典树的数据结构
2. 字典树的核心思想
3. 字典树的基本性质
1. 树Tree
按层次打印一颗二叉树,
在树中深度优先搜索:ABDH I EJ CFG
广度优先搜索:A BC DEFG HIJ
2. 二叉搜索树
二叉搜索树是子树之间的关系,并不是儿子和父亲的关系。
任何一个节点它的左子树的所有节点都要小于这个根结点
它的右子树的所有节点都要大于根结点,且对于它的任何子树同样地以此类推,对于任何子树都满足这样的特性。
二叉搜索树是一个升序的序列,如果是中序遍历左根右: 123 5678 9 10 11 12 13 15
二叉搜索树主要解决的问题是查找的效率会更高,比如要查10,首先跟根结点比,10 > 9,左子树不用找了,一分为二,只需要找右子树即可,再比较13 > 10,左子树,11 > 10,继续左子树。
3. 字典树Trie
应用的场景:
搜索时词频的感应,由前缀来推后面的可能的词语。
搜索引擎的搜索关键词提示功能,为了方便快速输入,当在搜索引擎的搜索框中,输入要搜索的文字的某一部分的时候,搜索引擎就会自动弹出下拉框,里面是各种关键词提示。你可以直接从下拉框中选择你要
搜索的东西,而不用把所有内容都输入进去,一定程度上节省了我们的搜索时间。
像 Google、百度这样的搜索引擎,它们的关键词提示功能非常全面和精准,肯定做了很多优化,但万变不离其宗,底层基本的原理就是今天要讲的这种数据结构:Trie 树。
字典树不再是存储一个单词本身,而是把字符串拆成单个单个的字母,每个字母存在这个节点里边
3.1 基本结构
字典树,即 Trie 树,又称单词查找树或键树,是一种树形结构。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。
它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。
节点本身不存任何单词,它只存它要去到下一个路径上面,这个路径代表的字符,每个节点依次进行。
比如ten这个单词,t -> te -> ten ,常用的字符放根结点上,根据输入的不同字符,开始分叉。Trie树不是一个二叉树而是一个多叉树。
根据走到不同地方,就开始分到不同的地方即所谓的分流,比如第一个字符是i(以i开始,后边所有可能是单词都是以i开头),如果后边路径是n,还是n ..就是inn 小酒馆,但这个节点它并不存储inn这个单词,它表这个地方是根了,且这个地方有一个单词的终止符,要看它代表哪个单词要从根节点捋下来,最后捋到这个节点它所走过的路径,任何节点最后它代表的单词就是走过的这条边
3.2 基本性质
1. 结点本身不存完整单词;
2. 从根结点到某一结点,路径上经过的字符连接起来,为该结点对应的字符串;
3. 每个结点的所有子结点路径代表的字符都不相同。
3.3 节点存储额外信息
数字表示相应到这个结点所代表的单词,它统计的计数就放在这个地方,数字表这个单词出现的统计频次。按照统计的频次就可以给用户做相应的推荐。
3.4 节点的内部实现
上图只是abcdefg...,同时它还有大写的ABCDEFG...
每个结点指向下一个节点的不同指针,这里它的存储不再是left和right来表示左右结点了,它直接用相应的字符来指向下一个结点,比如第一个字符是a,应该走到这个结点去,
如果是简单单词不分大小写,可以认为这里是26个分叉,从a一直分到z;如果是整个字符串它的ASCII域是255,即255分叉。上图可以看做是26分叉的一颗多叉树。
到最后如果是叶子节点,就指向空。
最大可能情况变成26叉树,它的空间相对来说是消耗比较大的,一层出去就是26.
它单词的长度即深度,它查询的次数即这个单词有多少个字符。比如单词to,它查t 和o,2次。
Trie 树的核心思想是空间换时间。
利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。
3.5 实现一棵 Trie 树?
如何用代码来实现一个 Trie 树
Trie 树主要有两个操作,一个是将字符串集合构造成 Trie 树,即将字符串插入到 Trie 树的过程;
另一个是在Trie树中查询一个字符串。
Trie树的存储:
Trie 树是一个多叉树。二叉树中,一个节点的左右子节点是通过两个指针来存储的。那对于多叉树来说,怎么存储一个节点的所有子节点的指针呢?
一种经典的存储方式,借助散列表的思想,通过一个下标与字符一一映射的数组,来存储子节点的指针。
假设字符串中只有从 a 到 z 这 26 个小写字母,在数组中下标为 0 的位置,存储指向子节点 a 的指针,下标为 1 的位置存储指向子节点 b 的指针,以此类推,下标为 25 的位 置,存储的是指向的子节点 z 的指针。如果某个字符的子节点不存在,就在对应下标的位置存储 null。
在 Trie 树中查找字符串的时候,就可以通过字符的 ASCII 码减去“a”的 ASCII 码,迅速找到匹配的子节点的指针。比如,d 的ASCII 码减去 a的 ASCII 码就是 3,那子节点 d 的指针就存储在数组中下标为 3的位置中。
代码实现:
/** * Trie 树也叫“字典树”,是一种专门处理字符串匹配的树形结构,用来解决在一组字符串集合中快速查找某个字符串的问题。 * */ public class Trie { private TrieNode root = new TrieNode('/'); // 存储无意义字符 // 往 Trie 树中插入一个字符串 public void insert(char[] text) { TrieNode p = root; for (int i = 0; i < text.length; ++i) { int index = text[i] - 'a'; if (p.children[index] == null) { TrieNode newNode = new TrieNode(text[i]); p.children[index] = newNode; } p = p.children[index]; } p.isEndingChar = true; } // 在 Trie 树中查找一个字符串 public boolean find(char[] pattern) { TrieNode p = root; for (int i = 0; i < pattern.length; ++i) { int index = pattern[i] - 'a'; if (p.children[index] == null) { return false; // 不存在 pattern } p = p.children[index]; } return p.isEndingChar; } public class TrieNode { public char data; public TrieNode[] children = new TrieNode[26]; public boolean isEndingChar = false; public TrieNode(char data) { this.data = data; } } }