前缀树结构(Trie)是一种比较特别的数据结构,用来存多个字符串,如果你想查找以某个前缀开头的字符串有几个?或者某个字符串出现了多少次?那么它就派上用场了。
思路:简单得说,我们用链表的底层来做这个前缀树,每个Node有3个属性,
pass:经过这个节点的时候pass+1
end:当到达最后节点end+1
next:当前节点的下个节点,可能有多个,所以我们用HashMap<Integer,Node>来存储,key是ASCII码(节点值),value是下个节点对象。
每当我们存一个字符串,遍历每个字符,字符也就是节点,经过则pass++,最后个字符结束则end++。当我们查询前缀出现了几次,直接遍历完前缀字符串,取最后一位的pass值就是前缀的频次数了。当我们要查找某个完整字符串的频次,就遍历完字符串,取最后一位的end值即可。如下图所示:加了a和ab两个字符串
下面我们用java实现一个前缀树,包含添加、查找前缀频次,字符串频次,字符串个数,删除字符串的功能。
package class05;
import java.util.HashMap;
/**
* @Description 前缀树求频次
* @Package: class05
* @Author: Ray
* @CreateTime: 2020/7/19 16:33
*/
public class Test2 {
/**
* 节点对象,也就是字符串中的字符
*/
public static class Node {
int pass = 0;
int end = 0;
HashMap<Integer, Node> next = null;
public Node() {
next = new HashMap<>();
}
}
/**
* 前缀树class
*/
public static class Trie {
private Node root = null;
public Trie() {
root = new Node();
}
/**
* 总共加入的字符串个数
* @return
*/
public int size() {
return root.pass;
}
/**
* 添加字符串
* @param str
*/
public void insert(String str) {
if (str == null || "".equals(str)) {
return;
}
int index = 0; //字符的ASCII码值
Node node = root;
node.pass++;
char[] chars = str.toCharArray(); //转成char数组
for (char c : chars) {
index = c; //当前字符作为节点
if (node.next.get(index) == null) { //如果节点没有则创建
node.next.put(index, new Node());
}
node = node.next.get(index); //节点向下走一个
node.pass++;
}
node.end++; //最后一个节点循环完之后,end+1
}
/**
* 从前缀树删除一个字符串
* @param str
*/
public void delete(String str) {
if (search(str) > 0) { //存在才删除
int index = 0; //字符的ASCII码值
char[] chars = str.toCharArray();
Node node = root;
node.pass--; //沿途pass--
for (char c : chars) {
index = c;
//如果某节点pass--后是0,则表示没有经过此节点,那么该节点及子节点都没用了
if (--node.next.get(index).pass == 0) {
node.next.remove(index);
return;
}
node = node.next.get(index);
}
node.end--; //沿途pass--,最后一个end--就算删除了
}
}
/**
* 查找字符串出现的频次
* @param str
* @return
*/
public int search(String str) {
int index = 0; //字符的ASCII码值
Node node = root;
char[] chars = str.toCharArray();
for (char c : chars) {
index = c;
//树里没有下一个字符了,那么这个字符串没出现过,返回0次
if (node.next.get(index) == null) {
return 0;
}
node = node.next.get(index); //节点树往下走一个字符
}
return node.end; //因为每添加一个字符串,末尾都end+1,这里直接返回end就是频次了
}
/**
* 查找前缀出现的频次
* @param str
* @return
*/
public int prefixCount(String str) {
int index = 0; //字符的ASCII码值
Node node = root;
char[] chars = str.toCharArray();
for (char c : chars) {
index = c;
//树里没有下个前缀字符了,那么这个前缀就没出现过,返回0次
if (node.next.get(index) == null) {
return 0;
}
node = node.next.get(index); //节点树往下走一个字符
}
//因为每添加一个字符串,里面的每个字符都pass++,那么返回前缀最后一位的pass就是它出现的频次
return node.pass;
}
}
public static void main(String[] args) {
Trie trie = new Trie();
trie.insert("abc");
trie.insert("abc");
trie.insert("abce");
trie.insert("abce");
trie.delete("abce");
System.out.println(trie.size());
System.out.println(trie.search("abc"));
System.out.println(trie.prefixCount("ab"));
}
}