前缀树结构(Trie)是一种比较特别的数据结构,用来存多个字符串,如果你想查找以某个前缀开头的字符串有几个?或者某个字符串出现了多少次?那么它就派上用场了。


思路:简单得说,我们用链表的底层来做这个前缀树,每个Node有3个属性,

pass:经过这个节点的时候pass+1

end:当到达最后节点end+1

next:当前节点的下个节点,可能有多个,所以我们用HashMap<Integer,Node>来存储,key是ASCII码(节点值),value是下个节点对象。

每当我们存一个字符串,遍历每个字符,字符也就是节点,经过则pass++,最后个字符结束则end++。当我们查询前缀出现了几次,直接遍历完前缀字符串,取最后一位的pass值就是前缀的频次数了。当我们要查找某个完整字符串的频次,就遍历完字符串,取最后一位的end值即可。如下图所示:加了a和ab两个字符串

java设置字典可以干啥用 java实现字典树_java


下面我们用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"));

	}
}