一: 概念
trie树,也被称为是单词查找树,可以理解为是hash树的变种(不过trie树的实现和hash没有关系哦)。常被用来统计排序和保存大量的文本,搜索引擎系统也会用来进行文本词频的统计
二:优劣
优点: 利用字符串的公共前缀来减少查询时间,最大限度的减少无谓的字符串的比较。查询效率比hash要高
缺点:占用的空间比较大
三:特点
1. root节点不存储字符
2. 除了root节点之外每一个节点都表示一个字符。一个节点的子节点们表示的字符相互独立,没有重复
3. 从root节点到某一个节点通过的路径连接起来就表示这个节点所表示的字符串
四:图例
![](https://img-blog.csdnimg.cn/20210718221302643.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2phdmFkb2Nkb2M=,size_16,color_FFFFFF,t_70)
图1-1
![](https://img-blog.csdnimg.cn/20210718221009199.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2phdmFkb2Nkb2M=,size_16,color_FFFFFF,t_70)
图1-2
如上图所存储的是an,hi,hl,high,man 5个字符串
插入步骤:
图1-1 为插入前的trie树,图1-2为插入后的trie树
目标:插入字符串high
- 查看root节点的子节点是否包含第一个字符h,发现包含,则继续查看h的子节点是否包含下一个节点i,以此类推
- 发现h节点的子节点中包含i节点,继续找下一层
- 从i节点的子节点中找不到g节点,那么就创建子节点g
- 继续在g节点下查找h节点,发现找不到,那么创建h节点作为g节点的子节点
- high的4个字符均查找结束,因为最后一个h节点是存储字符串的结束,所以需要置为结束态。结果图如1-2
查找步骤:
从图1-2中查找high字符串
- 因为root节点不存储数据,所以查找root节点的子节点是否包含第一个字符h,如果不包含,那么认为没有存储high字符串;
- 如果root的子节点包含h字符,那么继续从h字符的子节点查找下一个字符i,以此类推
- 直到找到最后一个字符h,判断该字符是否是结尾字符,如果是那么就包含high,如果不是,则不包含
五:复杂度分析
时间复杂度:不管是insert还是search操作,时间复杂度都是O(n),n为insert或search目标字符串的长度
空间复杂度:从下面的代码中可以看到每一个节点都需要一个固定大小的数组来表示子节点列表,数组的大小由我们想要存储的字符种类数来决定,在例子,只考虑26个英文字母,所以设置的26的大小。因为不管是否有子节点,子节点所占据的空间都是固定的,所以占据空间较大。
六:代码
java 版本
class TrieNode {
// 当前节点存储的值
char value;
// 子节点列表
TrieNode[] son = new TrieNode[26];
// 是否是结尾节点
boolean isEnd;
// 记录经过该条路径的字符串个数
int num;
@Override
public String toString() {
return "TrieNode{" +
"value=" + value +
", son=" + Arrays.toString(son) +
", isEnd=" + isEnd +
'}';
}
}
public class Test {
// 根节点
TrieNode root = new TrieNode();
Test() {
root.isEnd = false;
}
/**
* 插入指定的字符串
*
* @param str
*/
public void insert(String str) {
TrieNode node = root;
// 如果插入操作的字符串是空字符串,那么无需执行后续操作
if (null == str || str.length() == 0) {
return;
}
char[] chars = str.toCharArray();
for (int i = 0; i < chars.length; i++) {
int position = chars[i] - 'a';
if (null == node.son[position]) {
TrieNode child = new TrieNode();
child.value = chars[i];
node.son[position] = child;
}
node = node.son[position];
node.num++;
}
node.isEnd = true;
}
// 判断trie树中是否包含某个字符串
public boolean has(String str) {
if (str == null || str.length() == 0) {
return true;
}
char[] chars = str.toCharArray();
TrieNode node = root;
for (int i = 0; i < chars.length; i++) {
int position = chars[i] - 'a';
if (node.son[position] == null) {
return false;
}
node = node.son[position];
}
if (node.isEnd) {
return true;
} else {
return false;
}
}
/**
* 判断存储指定字符串前缀的字符串个数
*
* @param prefix
* @return
*/
public int countPrefix(String prefix) {
int count = 0;
if (null == prefix || prefix.length() == 0) {
return count;
}
char[] chars = prefix.toCharArray();
TrieNode node = root;
for (int i = 0; i < chars.length; i++) {
int position = chars[i] - 'a';
if (node.son[position] == null) {
return count;
}
node = node.son[position];
}
return node.num;
}
public static void main(String[] args) {
Test test = new Test();
test.insert("ahdjskbhk");
test.insert("ahdsw");
test.insert("ahdsws");
System.out.println(test.root);
System.out.println(test.has("ahdjskb"));
System.out.println(test.countPrefix("ahdsw"));
}
}