Trie 字典树的两种实现方式

Trie,又称字典树、单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。

上图是一棵Trie树,表示了关键字集合{"a", "to", "tea", "ted", "ten", "i", "in", "inn"} 。从上图可以归纳出Trie树的基本性质:

  • 根节点不包含字符,除根节点外的每一个子节点都包含一个字符。
  • 从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串。
  • 每个节点的所有子节点包含的字符互不相同。
  • 从第一字符开始有连续重复的字符只占用一个节点,比如上面的to,和ten,中重复的单词t只占用了一个节点。

字典树的实现有递归与非递归的两种方式:

非递归:

非递归的实现也有两种方式,一种是利用自己创建的节点来实现,具体实现如下:

java 复制代码
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内存储着每一个字符与下一个节点的对应关系。

当然,也有另一种实现方式:

java 复制代码
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[]数组,由此实现节点的对应关系。

递归
java 复制代码
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);
    }
}
相关推荐
脸大是真的好~1 天前
黑马JAVAWeb-01 Maven依赖管理-生命周期-单元测试
java·maven
zhangkaixuan4561 天前
Apache Paimon 查询全流程深度分析
java·apache·paimon
cici158741 天前
MyBatis注解的运用于条件搜索实践
java·tomcat·mybatis
wangqiaowq1 天前
StarRocks安装部署测试
java·开发语言
计算机学姐1 天前
基于SpringBoot的高校社团管理系统【协同过滤推荐算法+数据可视化】
java·vue.js·spring boot·后端·mysql·信息可视化·推荐算法
沙威玛_LHE1 天前
树和二叉树
数据结构·算法
py有趣1 天前
LeetCode算法学习之两数之和 II - 输入有序数组
学习·算法·leetcode
夏鹏今天学习了吗1 天前
【LeetCode热题100(62/100)】搜索二维矩阵
算法·leetcode·矩阵
工业甲酰苯胺1 天前
实现 json path 来评估函数式解析器的损耗
java·前端·json
老前端的功夫1 天前
Web应用的永生之术:PWA落地与实践深度指南
java·开发语言·前端·javascript·css·node.js