一、引例
1、给出n个单词和m个询问,每次询问一个单词,回答这个单词是否在单词表中出现过。
2、给出n个单词和m个询问,每次询问一个字母序列,回答它是多少个单词的前缀。
设计自己的单词存储方式,实现插入、查询以下几个单词。
cat,cash,app,apple,aply,ok。
也许你的第一个想法是暴力搜索每一个单词,但是复杂度很很很很高啊
有一种东西叫做字典树,字典树的根节点连出来的每一条边存的就是出现的首字母,
然后但此后边的字母我们可以在后边存上, 这样每一个单词的最后一个单词就是最后存的
那条边,这样我们可以在那条代表着最后单词的边的下一个节点处打一个标记,然后表示一个单词就结束了;
like this
把每一个单词放到字典树中...
二、基本操作
插入操作:
我们插入字符串的时候就每个边存一个字符,然后大概就是这个亚子:
void add(string ch) {
int len = ch.size(), root = 0, x;
for (int i = 0; i < len; i++) {
x = ch[i] - 'a';
if (!trie[root][x]) trie[root][x] = ++tot;
tr[trie[root][x]] = 1;
root = trie[root][x];
}
tr[trie[root][x]] = 1;
}
修改操作:
应该都能看懂吧
int query(string ch) {
int len = ch.size(), root = 0, ans = 0;
int x;
for (int i = 0; i < len; i++) {
x = ch[i] - 'a';
if (trie[root][x] == 0) return 0;
ans = tr[trie[root][x]];
tr[trie[root][x]] = 2;
root = trie[root][x];
}
return ans;
}
三、练习
板子类
1.洛谷 P2580 于是他错误的点名开始了
大体思路:
我们可以吧每一个人的名字一 一存入字典树中。然后查询操作的时候每查询一个单词
之后把每一个单词之后的节点都覆盖掉,然后下一次如果又有这个单词,那么就查不到了
上代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
int tot, trie[400001][31], tr[400001];
string ch;
void add(string ch) {
int len = ch.size(), root = 0, x;
for (int i = 0; i < len; i++) {
x = ch[i] - 'a';
if (!trie[root][x]) trie[root][x] = ++tot;
tr[trie[root][x]] = 1;
root = trie[root][x];
}
tr[trie[root][x]] = 1;
}
int query(string ch) {
int len = ch.size(), root = 0, ans = 0;
int x;
for (int i = 0; i < len; i++) {
x = ch[i] - 'a';
if (trie[root][x] == 0) return 0;
ans = tr[trie[root][x]];
tr[trie[root][x]] = 2;
root = trie[root][x];
}
return ans;
}
int main() {
int n, m;
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
cin>>ch;
add(ch);
}
scanf("%d", &m);
for (int i = 1; i <= m; i++) {
cin >> ch;
if (query(ch) == 1) printf("OK
");
else if (query(ch) == 0) printf("WRONG
");
else if (query(ch) == 2) printf("REPEAT
");
}
}
字符串匹配类(往下我就不一 一讲了自己弄明白才是王道)
异或类
优化类
还是看不懂的小伙伴们看这里
may be 这个比我都讲得好