1. 前缀树介绍
又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。
前缀树,就是根据字符串中的每一个字符来到达相应的结点,所以比较适合统计或者查找字符串。如果使用数组存储字符串的话,每次统计或者查找,都要遍历整个数组,而是用前缀树存储字符串,只需要根据字符串中的每个字符找,时间复杂度和要查找的字符串有关,和字符串的数量无关。另外,通过前缀树,还可以非常快捷的查以xxx开头的字符串个数,如果想利用前缀树实现其他功能,可以在节点中添加其他属性。
2. 前缀树结构
比如使用前缀树,存储字符串"ewen"
如果要存储其他的字符串,就从根节点开始根据字符串中的字符找,如果能根据字符找到结点就继续往下找,找不到,就新建边和结点,比如再存储字符串"ewmm"
3. 前缀树代码实现
public class Code_01_TrieTree {
//定义前缀树结点
public static class TrieNode{
//代表有多少字符串经过这个结点
public int pass;
//代表有多少个字符串以这个结点结尾
public int end;
/*代表从这个结点经过某一个字符可以到达的结点,也就是从这个结点开始的边的个数,
加入这个前缀树存储的都是小写字母的话,将素组大小设为26,这样,字母a-z,就对应
数组中索引为0-25。如果字符串的组成不是小写字母,比如是中文字符串,可以使用一个
hashMap<Integer,TrieNode>key是字母的ASCII码,value,是从这个结点开始,通过
key能到达的结点。
*/
public TrieNode[] nexts;
public TrieNode(){
pass = 0;
end = 0;
nexts = new TrieNode[26];
}
}
//树
public static class Trie{
//定义一个根结点
private TrieNode root = new TrieNode();
//向前缀树中插入字符串
public void insert(String word){
if(word==null||word.length()==0) return;
//先把word变成字符类型的数组
char[] chars = word.toCharArray();
TrieNode node = root;
//index为字母对应的数组索引
int index = 0;
for(int i = 0; i < chars.length; i++){
index = chars[i] - 'a';
//判断从root结点,能不能通过字母走向下一个结点
if(node.nexts[index] == null){
//如果从root节点,不能通过字母走向下一个结点
node.nexts[index] = new TrieNode();
}
//如果从root结点,可以通过字母到下一个结点
node = node.nexts[index];
node.pass++;
}
//最后一个结点的end++,表示以这个结点结尾的字符串数
node.end++;
}
//查询一个字符串出现的次数
public int search(String word){
if(word == null || word.length() == 0) return 0;
//先将字符串变成字符数组
char[] chars = word.toCharArray();
TrieNode node = root;
int index = 0;
for(int i = 0; i < chars.length; i++){
index = chars[i] - 'a';
//只要结点的nexts数组的index下没有存结点,就说明前缀树中没有存储这个字符串
if(node.nexts[index] == null){
return 0;
}
node = node.nexts[index];
}
//返回最后一个结点的end值
return node.end;
}
//前缀树删除一个字符串
public void delete(String word){
//首先查询寻前缀树中是否有字符串
if(search(word) > 0){
//将字符串转换成字符数组
char[] chars = word.toCharArray();
TrieNode node = root;
int index = 0;
for(int i = 0; i < chars.length; i++){
/*判断当前结点的下一个结点的path值是否为1,为1,说明只有一个字符串
经过下一个结点,直接将当前结点的index位置设为null,如果不为1,说明
还有其他字符串经过下一个结点,也就是说,字符串有相同的开头,那么就让
下一个结点的path--,继续往下寻找,知道找到下一个结点的path为1的结点,
将结点的nexts的index位置设为null
*/
index = chars[i] - 'a';
if(node.nexts[index].pass-- == 1 ){
//说明只有一个字符串经过这个结点,直接将数组对应的索引位置设为null
node.nexts[index] = null;
return;
}
//如果有两个或两个以上的字符串经过结点,那么这个结点
node = node.nexts[index];
//当添加多个相同的字符串,并且删除其中一个的时候,会执行--操作
node.end--;
}
}
}
//查找指定前缀的字符串个数
public int samePrefixNum(String word){
if(word == null || word.length() == 0) return 0;
char[] chars = word.toCharArray();
int index = 0;
TrieNode node = root;
for(int i = 0; i < chars.length; i++){
index = chars[i] - 'a';
//中间有一个找不到,就返回0
if(node.nexts[index] == null){
return 0;
}
node = node.nexts[index];
}
return node.pass;
}
}
public static void main(String[] args) {
Trie trieTree = new Trie();
System.out.println(trieTree.search("ewen"));//0
trieTree.insert("ewen");
System.out.println(trieTree.search("ewen"));//1
trieTree.insert("ewen");
trieTree.insert("ewen");
trieTree.insert("ewen");
System.out.println(trieTree.search("ewen"));//4
trieTree.delete("ewen");
System.out.println(trieTree.search("ewen"));//3
System.out.println(trieTree.samePrefixNum("ew"));//3
}
}