Trie,又称字典树、单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。
上图是一棵Trie树,表示了关键字集合{“a”, “to”, “tea”, “ted”, “ten”, “i”, “in”, “inn”} 。从上图可以归纳出Trie树的基本性质:
- 根节点不包含字符,除根节点外的每一个子节点都包含一个字符。
- 从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串。
- 每个节点的所有子节点包含的字符互不相同。
- 从第一字符开始有连续重复的字符只占用一个节点,比如上面的to,和ten,中重复的单词t只占用了一个节点。
字典树的实现有递归与非递归的两种方式:
非递归:
非递归的实现也有两种方式,一种是利用自己创建的节点来实现,具体实现如下:
public class Trie {public static void main(String[] args) {Trie trie = new Trie();trie.add("apple");System.out.println(trie.contains("apple"));System.out.println(trie.isPrefix("app"));}private class TreeNode{public boolean isWord;public TreeMap<Character,TreeNode> next;public TreeNode(){this(false);next = new TreeMap<>();}public TreeNode(boolean isWord){this.isWord = isWord;}}private TreeNode root;private int size;public Trie(){this.root = new TreeNode();this.size = 0;}public int getSize(){return size;}public void add(String str){TreeNode cur = root;for(int i=0;i<str.length();i++){char c = str.charAt(i);if(cur.next.get(c) == null){//新建节点cur.next.put(c, new TreeNode());}//否则,就直接走到该节点位置即可cur = cur.next.get(c);}if(!cur.isWord){//确定cur是新的单词cur.isWord = true;size++;}}public boolean contains(String str){TreeNode cur = root;for(int i=0;i<str.length();i++){char c = str.charAt(i);if(cur.next.get(c) == null) return false;cur = cur.next.get(c);}return cur.isWord;}public boolean isPrefix(String prefix){TreeNode cur = root;for(int i=0;i<prefix.length();i++){char c = prefix.charAt(i);if(cur.next.get(c) == null) return false;cur = cur.next.get(c);}return true;}}
这种实现方式是利用自己创建的节点实现的。每一个Trie对象内部都存在一个TreeNode对象与size对象。其中TreeNode对象内部存在着一个TreeMap,TreeMap内存储着每一个字符与下一个节点的对应关系。
当然,也有另一种实现方式:
class Trie {private Trie[] children;private boolean isWord;public Trie() {children = new Trie[26];isWord = false;}public void insert(String word) {Trie node = this;for(char c : word.toCharArray()){int idx = c - 'a';if(node.children[idx] == null){node.children[idx] = new Trie();}node = node.children[idx];}node.isWord = true;}public boolean search(String word) {Trie node = searchPrefix(word);return node != null && node.isWord;}public boolean startsWith(String prefix) {return searchPrefix(prefix) != null;}public Trie searchPrefix(String word){Trie node = this;for(char c : word.toCharArray()){int idx = c - 'a';if(node.children[idx] != null){node = node.children[idx];}else{return null;}}return node;}
}/*** Your Trie object will be instantiated and called as such:* Trie obj = new Trie();* obj.insert(word);* boolean param_2 = obj.search(word);* boolean param_3 = obj.startsWith(prefix);*/
这个实现方式是每一个Trie对象内部存储自己的Trie[]数组,由此实现节点的对应关系。
递归
public void recursionAdd(String word) {Node cur = root;add(root, word, 0);
}/*** 递归写法调用方法实现递归添加** @param node 传入要进行添加的节点* @param word 传入要进行添加的单词*/
public void add(Node node, String word, int index) {// 确定终止条件,这个终止条件在没加index这个参数时,很难确定// 此时一个单词已经遍历完成了,如果这个结束节点没有标记为单词,就标记为单词if (!node.isWord && index == word.length()) {node.isWord = true;size++;}if (word.length() > index) {char addLetter = word.charAt(index);// 判断trie的下个节点组中是否有查询的字符,如果没有,就添加if (node.next.get(addLetter) == null) {node.next.put(addLetter, new Node());}// 基于已经存在的字符进行下个字符的递归查询add(node.next.get(addLetter), word, index + 1);}
}