1167 字
5 分钟
Trie

定义#

Trie ,中文名字典树,又称前缀树,顾名思义,是一个像字典一样的树。

这是一棵 Trie。

实现方式#

最开始我们有一棵空的字典树。

对于每一个待插入的字符串,我们都从根节点开始,使用每条边表示一个字符。 若当前节点下有所需字符所对应的边,则顺应边前进,继续操作下一个字符。 若当前节点下没有所需的字符所对应的边,则新建边来使用。

按照以上逻辑,每次从字符串中取一个字符来建边或前进,直到字符串结束。

性质#

易得,对于每个长度为 nn 的字符串 SS,我们可以在 O(n)O(n) 的时间内检索它是否存在。

同时,对于每一个存在的字符串 SS,其前缀字符串 ss 也可以被检索到。 如果不想使得子字符串被检索,可以在每个字符串结尾的节点处打标记。

虽然通常用字典树来存字符串,但是我们完全可以把它当作一种处理有序信息序列的数据结构,例如一串数字或形状的排列。实际上,在竞赛中如果单独使用字典树的话,确实也是当作数据结构更多一些。

代码#

这里我为了泛用性,导致代码很长,实际使用中只需要按需编写即可,最重要的还是理解算法。

template <typename T> struct Trie {
    struct Node {
        unordered_map<T, int> child;
        //如果字符集固定,例如只有小写字母,则使用 array<int,26> 通常更快。
        int pass = 0, end = 0;
    };
    vector<Node> tree;
    Trie() { tree.emplace_back(); }
    template <typename Iterator> void insert(Iterator begin, Iterator end) {
        int u = 0;
        tree[u].pass++;
        for (auto it = begin; it != end; it++) {
            const T& c = *it;
            auto pos = tree[u].child.find(c);
            if (pos == tree[u].child.end()) {
                int id = tree.size();
                tree[u].child[c] = id;
                tree.emplace_back();
                u = id;
            } else
                u = pos->second;
            tree[u].pass++;
        }
        tree[u].end++;
    }
    template <typename Iterator> int query(Iterator begin, Iterator end) {
        int u = 0;
        for (auto it = begin; it != end; it++) {
            const T& c = *it;
            auto pos = tree[u].child.find(c);
            if (pos == tree[u].child.end())
                return -1;
            u = pos->second;
        }
        return u;
    }
    // 以上实现必要功能,以下为附加内容
    template <typename Container> void insert(const Container& s) { insert(s.begin(), s.end()); }
    template <typename Iterator> bool exists(Iterator begin, Iterator end) {
        int u = query(begin, end);
        return u != -1 && tree[u].end > 0;
    }
    template <typename Iterator> bool existsPrefix(Iterator begin, Iterator end) {
        int u = query(begin, end);
        return u != -1 && tree[u].pass > 0;
    }
    template <typename Iterator> int count(Iterator begin, Iterator end) {
        int u = query(begin, end);
        if (u == -1)
            return 0;
        return tree[u].end;
    }
    template <typename Iterator> bool erase(Iterator begin, Iterator end) {
        vector<int> path;
        int u = 0;
        path.emplace_back(0);
        for (auto it = begin; it != end; it++) {
            const T& c = *it;
            auto pos = tree[u].child.find(c);
            if (pos == tree[u].child.end())
                return 0;
            u = pos->second;
            path.emplace_back(u);
        }
        if (tree[u].end == 0)
            return 0;
        tree[u].end--;
        for (int id : path)
            tree[id].pass--;
        return 1;
    }
};

应用#

事实上,相较于代码,更重要的是它的应用。

最基础的应用,我们可以查找一个字符串是否出现过,出现过几次。

我们也可以用 Trie 来构建 AC 自动机

用一棵字符集为 {0,1}\{0,1\} 的 01-Trie 来维护数字的异或关系。

这些问题我会在日后补充,其实本篇笔记和昨天的 KMP 主要是为 AC 自动机做铺垫 )。

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Trie
https://leaf146.cn/posts/trie
作者
LeAf146
发布于
2026-08-02
许可协议
MIT

部分信息可能已经过时