字典树(Trie)
-
字典树
例题
原理
本质上是存储若干个字符串的公共前缀以减少重复的查询操作,达到提高查询效率的目的。
(本题中的字符串只含数字与大小写字母,所以一共有62个字符)
\(N\) 为字符串最大长度。
ch[N][65]:ch[p][j]存储从节点 \(p\) 沿着值为 \(j\) 的这条边走到的子节点,从 'a' 到 'z','A' 到 'Z' ,'0' 到 '9' 一共 \(62\) 个字符,对应映射值 \(0 - 61\) ,每个节点最多 \(62\) 个分叉。
cnt[N]:cnt[p] 用来存储节点 \(p\) 在插入操作中被访问的次数。
idx:idx 用于给节点编号。
插入操作
初始阶段仅有一个空节点,编号为 \(0\),不存储任何数据。从根节点开始插入,枚举字符串的每个字符,如果当前节点有子节点是待插入的下一个字符,就走到子节点。如果没有满足的子节点,就先创建子节点,然后走到这个子节点。每次走到子节点的时候,就将这个节点的计数器 \(+1\)。
查询操作
从根开始查,扫描字符串。如果有字符 s[i],则继续往下走,能走到词尾,则返回访问次数;无字符 s[i],则返回 \(0\).
代码
#include <iostream>
#include <cstring>
using namespace std;
const int N = 3000005;
char str[N];
int ch[N][65], cnt[N], idx;
int getnum(char c)
{//映射字符
if (c >= 'a' && c <= 'z')return c - 'a';
else if (c >= 'A' && c <= 'Z')return c - 'A' + 26;
else if (c >= '0' && c <= '9')return c - '0' + 52;
else return -1;
}
void insert(char s[])
{//插入操作
int p = 0;
for (int i = 0; s[i]; i++)
{
int j = getnum(s[i]);
if (!ch[p][j])ch[p][j] = ++idx;
p = ch[p][j];
cnt[p]++;//将cnt[p]++放在循环内部是因为题目要求查询前缀中含有待查询序列的字符串
}
//如果目的是查询与被查询字符串相等的字符串的个数,就将插入操作改为
/*
int p = 0;
for (int i = 0; s[i]; i++)
{
int j = getnum(s[i]);
if (!ch[p][j])ch[p][j] = ++idx;
p = ch[p][j];
}
cnt[p]++;
*/
}
int query(char s[])
{//查询操作
int p = 0;
for (int i = 0; s[i]; i++)
{
int j = getnum(s[i]);
if (!ch[p][j])return 0;
p = ch[p][j];
}
return cnt[p];
}
void solve()
{
int n, q;
cin >> n >> q;
//清空之前使用过的数组
for (int i = 0; i <= idx; i++)
{
for (int j = 0; j < 65; j++)ch[i][j] = 0;
cnt[i] = 0;
}
idx = 0;
for (int i = 1; i <= n; i++)
{
cin >> str;
insert(str);
}
for (int i = 1; i <= q; i++)
{
cin >> str;
cout << query(str) << endl;
}
}
int main()
{
ios::sync_with_stdio(0);
int T;
cin >> T;
while (T--)
{
solve();
}
return 0;
}

浙公网安备 33010602011771号