Skip to content

🍕 前缀树

前缀树作用:

  • 查询某字符串数量
  • 查询以某字符为前缀的字符串数量

操作:

  • 往前缀树中插入一个字符串
  • 查找一个字符串的数量 (end)
  • 查找某前缀的字符串的数量 (pass)
  • 删除某个字符串
    • 方法1: 先查找, 看看这个字符存不存在, 然后再删除
    • (我认为的)方法2: 同样先查找, 但是查找过程中保存走过的路径, 如果找到, 那就就直接在路径上删除。
    • 方法2: 其实和 方法1 是一样的, 在时间复杂度上是一样的, 常数时间上方法2快一点点, 但是占用了更多的内存。
java
class TrieNode {
    int pass; // 构造前缀树时经过了这个节点多少次
    int end;  // 这个节点是多少个字符串的最后一个字符
    TrieNode[] nexts; // 这个节点的后面有多少个节点。 如果字符串是纯小写字母的话, nexts 数组长度为 26 初始值为 Null, 表示不存在这条路
    // 刷题过程中的, 字符的种类基本是固定的, 所以这里使用固定长度的数组形式
    // 实际项目中, 字符的种类可能是未知的, 这个时候可以使用 哈希表 的, key 就是字符, value 就是对应的 TrieNode 节点
}

class Trie {
    TrieNode root;

    Trie() {
        // 根节点上的 pass 表示的含义是以 空串 为前缀的数量, 也就是加入的字符串数量
        //          end  的含义就是 空字符串 的数量
        root = new TrieNode();
    }

    // 插入一个字符串
    void insert(String word) {
        if (word == null) {
            return;
        }
        char[] chs = word.toCharArray(); // 将字符串拆分成单个字符
        TrieNode node = root;
        node.pass++;
        int index = 0;
        for (int i = 0; i < chs.length; i++) { // 遍历单个字符, 依次建路
            index = chs[i] - 'a';
            if (node.nexts[index] == null) {
                node.nexts[index] = new TrieNode()
            }
            node = node.nexts[index];
            node.pass++;
        }
        node.end++;
    }

    // 查找某个字符的数量
    int search(String word) {
        if (word == null) {
            return 0;
        }
        char[] chs = word.toCharArray();
        TrieNode node = root;
        int index = 0;
        for (int i = 0; i < chs.length; i++) {
            index = chs[i] - 'a';
            if (node.nexts[index] == null) {
                return 0;
            }
            node = node.nexts[index]
        }
        return node.end;
    }

    // 查询以某个字符为前缀的数量。 和 search 的区别只在于, 查询前缀返回的是 pass
    int prefixNumber(String word) {
        if (word == null) {
            return 0;
        }
        char[] chs = word.toCharArray();
        TrieNode node = root;
        int index = 0;
        for (int i = 0; i < chs.length; i++) {
            index = chs[i] - 'a';
            if (node.nexts[index] == null) {
                return 0;
            }
            node = node.nexts[index]
        }
        return node.pass;
    }

    void delete(String word) [
        if (search(word) != 0) {
            char[] chs = word.toCharArray();
            TrieNode node = root;
            node.pass--;
            int index = 0;
            for (int i = 0; i < chs.length; i++) {
                index = chs[i] - 'a'
                if (--node.nexts[index].pass == 0){
                    // 如果是 c++, 需要继续访问后续的节点, 将他们的空间释放。 具体做法可以是: 不在这里释放空间, 而是先记录下来, 同时把后续要释放空间的节点记录下来
                    // 等到循环结束后, 再将节点设置为空, 同时遍历要释放空间的节点, 依次释放空间。
                    node.nexts[index] = null;
                    return;
                }
                node = node.nexts[index];
            }
            node.end--;
        }
    ]

}