zl程序教程

您现在的位置是:首页 >  后端

当前栏目

【数据结构】前缀树/字典树

数据结构 字典 前缀
2023-09-27 14:25:46 时间

本文参考:
LeetCode 208.实现 Trie (前缀树)

1.概述

前缀树又称字典树、Trie 树、单词查找树,是一棵有根树,同时也是一种哈希树的变种,其每个节点包含以下字段:

  • 指向子节点的指针数组 children。一般来说,数组长度为 26,即小写英文字母的数量(也可根据实际情况自行设置)。此时 children[0] 对应小写字母 a,children[1] 对应小写字母 b,…,children[25] 对应小写字母 z。
  • 布尔字段 isEnd,表示该节点是否为字符串的结尾

2.代码实现

(1)Trie 树的代码实现如下:

class Trie {
    private Trie[] children;
    private boolean isEnd;
    
    //构造函数
    public Trie() {
        //每个节点最多有 26 个子节点,分别对应 26 个小写英文字母
        children = new Trie[26];
        isEnd = false;
    }
    
    //插入字符串
    public void insert(String word) {
        Trie node = this;
        for (int i = 0; i < word.length(); i++) {
            int index = word.charAt(i) - 'a';
            if (node.children[index] == null) {
                node.children[index] = new Trie();
            }
            node = node.children[index];
        }
        //当前节点 node 为 word 的结尾节点
        node.isEnd = true;
    }
    
    //判断单词 word 是否在前缀树种
    public boolean search(String word) {
        Trie node = searchPrefix(word);
        return node != null && node.isEnd;
    }
    
    //判断前缀 prefix 是否存在于前缀树中
    public boolean startsWith(String prefix) {
        return searchPrefix(prefix) != null;
    }
    
    //返回前缀 prefix 的最后一个字符所在的节点
    public Trie searchPrefix(String prefix) {
        Trie node = this;
        for (int i = 0; i < prefix.length(); i++) {
            int index = word.charAt(i) - 'a';
            if (node.children[index] == null) {
                return null;
            }
            node = node.children[index];
        }
        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);
 */

(2)下面对代码中的一些细节进行说明:

  • 插入字符串
    从字典树的根开始,插入字符串。对于当前字符对应的子节点,有以下两种情况:
    ① 子节点存在。沿着指针移动到子节点,继续处理下一个字符。
    ② 子节点不存在。创建一个新的子节点,记录在 children 数组的对应位置上,然后沿着指针移动到子节点,继续搜索下一个字符。
    重复以上步骤,直到处理字符串的最后一个字符,然后将当前节点标记为字符串的结尾,即令 isEnd = true
  • 查找前缀
    从字典树的根开始,查找前缀。对于当前字符对应的子节点,有以下两种情况:
    ① 子节点存在。沿着指针移动到子节点,继续搜索下一个字符。
    ② 子节点不存在。说明字典树中不包含该前缀,返回空指针。
    重复以上步骤,直到返回空指针或搜索完前缀的最后一个字符。若搜索到了前缀的末尾,就说明字典树中存在该前缀。此外,若前缀末尾对应节点的 isEnd 为 true,则说明字典树中存在该字符串

(3)复杂度分析:
① 时间复杂度:初始化为 O(1),其余操作为 O(|S|),其中 |S| 是每次插入或查询的字符串的长度
② 空间复杂度:O(∣T∣⋅Σ),其中 |T| 为所有插入字符串的长度之和,Σ 为字符集的大小,这里只存储所有的小写字母,即 Σ = 26。

(4)现在以下面的这些单词为例,来构造前缀树

 {"good", "gold", "send", "sence", "word", "world"}

分析这些单词后可以发现:
① “good” 和 “gold” 的公共前缀为 “go”;
② “send” 和 “sence” 的公共前缀为 “sen”;
③ “word” 和 “world” 的公共前缀为 “wor”;
最终构造的前缀树如下图所示:
在这里插入图片描述

3.应用

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

(2)大家可以去 LeetCode 上找相关的前缀树题目来练习,或者也可以直接查看 LeetCode算法刷题目录(Java)这篇文章中的前缀树章节。此外,如果大家发现文章中的错误之处,可在评论区中指出。