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);
    }
}
相关推荐
Q一件事2 分钟前
RWEQ计算——Wf因子
算法
zander2583 分钟前
LeetCode 64. 最小路径和
算法·深度优先
吴声子夜歌3 小时前
ApacheCommons——commons-pool2(高性能通用对象池框架)
java·开发语言·apache
xcl09258 小时前
幼儿托育系统开发实战:从需求分析到上线全流程指南
java·大数据·需求分析
吴声子夜歌8 小时前
ApacheCommons——commons-cli(命令行参数解析)
java·开发语言·apache
小小猪的春天9 小时前
Java 手写第一个 MCP Server:Spring AI MCP 半小时跑通
java·人工智能·spring boot·ai编程
find1star9 小时前
LeetCode 141:环形链表
java·算法·leetcode·链表
迅翼SwiftWing9 小时前
编队系列(总)|固定翼编队如何实现稳定飞行?通信架构、协同算法与工程约束深度解析
算法·架构
LB21129 小时前
力扣160 21 86
算法·leetcode·职场和发展
今天AI了吗9 小时前
DeepSeek Harness 深度解析:从评测架构到实战落地
java·网络·数据库·人工智能·架构·java-ee