By 虎皮玄椒
1167 字
5 分钟
Trie
定义
Trie ,中文名字典树,又称前缀树,顾名思义,是一个像字典一样的树。
实现方式
最开始我们有一棵空的字典树。
对于每一个待插入的字符串,我们都从根节点开始,使用每条边表示一个字符。 若当前节点下有所需字符所对应的边,则顺应边前进,继续操作下一个字符。 若当前节点下没有所需的字符所对应的边,则新建边来使用。
按照以上逻辑,每次从字符串中取一个字符来建边或前进,直到字符串结束。
性质
易得,对于每个长度为 的字符串 ,我们可以在 的时间内检索它是否存在。
同时,对于每一个存在的字符串 ,其前缀字符串 也可以被检索到。 如果不想使得子字符串被检索,可以在每个字符串结尾的节点处打标记。
虽然通常用字典树来存字符串,但是我们完全可以把它当作一种处理有序信息序列的数据结构,例如一串数字或形状的排列。实际上,在竞赛中如果单独使用字典树的话,确实也是当作数据结构更多一些。
代码
这里我为了泛用性,导致代码很长,实际使用中只需要按需编写即可,最重要的还是理解算法。
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 自动机。
用一棵字符集为 的 01-Trie 来维护数字的异或关系。
这些问题我会在日后补充,其实本篇笔记和昨天的 KMP 主要是为 AC 自动机做铺垫 )。
部分信息可能已经过时
