前言
当你在搜索条输入字符时,搜索引擎会根据你所输入的字符进行提示,这就是字典树的引用,他根据公共前缀来进行提示,可以减少不必要的比较。
本文将从代码层面让你彻底了解字典树,以最简单的字典树开始,以达到扩展的目的。
什么是字典树
字典树又被称为单词查找树或者前缀树,Trie,是一种树形结构。典型应用是用于统计,排序和保存大量的字符串。
它的优点是:利用字符串的公共前缀来减少查询时间,减少字符串的比较,查询效率比哈希树高。
性质
- 根节点不包含字符,除根结点外每一个结点都只包含一个字符。
- 从根节点到某一结点,路劲上经过的字符连接起来,为该点对应的字符串。(通过一个标志,来判断此字符是否为某字符串的结尾)
- 每个结点所有的子结点包含的字符都不相同。
代码详解
最简单的字典树,可以自行添加其他属性,
比如:当前结点所代表的字符,或者有没有子结点(这些都不是必需的)
属性 & 构造器
属性
Trie[] child;//保存下一个字符
boolean isEnd;//如果是字符串的结尾 则为tree
构造器
public Trie() {
child = new Trie[26];//总共有26个单词
isEnd = false;//根节点
}
child
:用一个数组来保存孩子结点。字符的数量为26,所以数组的大小也为26。如果某个位置为 null 则代表没有这个字符。
isEnd
:用来判断这个字符是不是字符串的结尾,如果是true,将其前面的路径连接起来就代表该字符串
insert 插入
public void insert(String word) {
//首先拿到根tire
Trie cur =this;
//循环插入
for (char ch : word.toCharArray()){
//计算当前字符在数组中的位置
int index = ch -'a';
//保存此字符 如果这和索引所在的位置不为null 就说名有字符
if (cur.child[index] ==null) {
cur.child[index] = new Trie();
}
cur = cur.child[index];
}
//退出循环时 cur指向字符串的末尾 设为true
cur.isEnd = true;
}
思路:
- 首先拿到根结点
- 计算字符在数组中的位置
- 循环插入字符
- 给末尾的字符,设置标志位为true
searchPrefix 搜索前缀
public Trie searchPrefix(String word){
//拿到根trie
Trie cur = this;
//遍历
for (char ch : word.toCharArray()){
//计算位置
int index = ch-'a';
//如果字符所代表的位置为空 则返回null
if (cur.child[index]==null){
return null;
}
cur = cur.child[index];
}
//返回最后一个字符所代表的tire
return cur;
}
思路:
- 拿到根节点
- 遍历字符,计算每个字符在数组中的位置
会有两种情况
为null,说明不存在此字符直接返回null
不为null,则遍历下一个结点 - 当遍历到最后一个结点时返回该节点
- 很明显如果能够返回结点,就说明该字符串为前缀。
- 可以根据该结点的标志为是否为 true,判断树中是否包含此字符串
判断是否为前缀
public boolean startsWith(String prefix) {
Trie node =searchPrefix(prefix);
return node!=null ;
}
判断是否包含某字符串
public boolean search(String word) {
Trie node =searchPrefix(word);
return node!=null && node.isEnd;
}
完整代码:
package com.dyit.tanruike;
//可以吧Trie看作一个结点
class Trie {
Trie[] child;//保存下一个字符
boolean isEnd;//如果是字符串的结尾 则为tree
public Trie() {
child = new Trie[26];//总共有26个单词
isEnd = false;//根节点
}
/**
* 插入操作
* @param word 需要插入的字符串
*/
public void insert(String word) {
//首先拿到根tire
Trie cur =this;
//循环插入
for (char ch : word.toCharArray()){
//计算当前字符在数组中的位置
int index = ch -'a';
//保存此字符 如果这和索引所在的位置不为null 就说名有字符
if (cur.child[index] ==null) {
cur.child[index] = new Trie();
}
cur = cur.child[index];
}
//退出循环时 cur指向字符串的末尾 设为true
cur.isEnd = true;
}
/**
*
* @param word 需要搜索的字符
* @return 如果找到了字符串所代表的最后一个字符 就返回字符 如果没有此单词 就返回null
*/
public Trie searchPrefix(String word){
//拿到根trie
Trie cur = this;
//遍历
for (char ch : word.toCharArray()){
//计算位置
int index = ch-'a';
//如果字符所代表的位置为空 则返回null
if (cur.child[index]==null){
return null;
}
cur = cur.child[index];
}
//返回最后一个字符所代表的tire
return cur;
}
/**
*
* @param word 判断是否保存此字符串
* @return 结果
*/
public boolean search(String word) {
Trie node =searchPrefix(word);
return node!=null && node.isEnd;
}
/**
* 判断此字符串是否为前缀
* @param prefix 需要判断的字符串
* @return 结果
*/
public boolean startsWith(String prefix) {
Trie node =searchPrefix(prefix);
return node!=null ;
}
}
可以根据实际情况,自行添加其他属性,以达到扩展的目的。