字典树Trie详解
哈喽大家好,我是 doooge ,今天给大家带来 Trie 的详解。
1.Trie是什么?
Trie 也叫字典树,前缀树,其本质就是一棵字符树。它也是 AC自动机 的一部分。
这是 Trie 的 oiwiki介绍。
Trie 通常用来解决查找一个字符串在某个集合里是否出现过,也可以排序,它的时间复杂度很优秀,
2.Trie的创建
如果需要在这棵树中查询,就必须要创建这棵树。如果我们要存储
在存储的过程中,我们不必存储每个节点的信息,只要记录它的下一步能到哪里去就行了。在没有字符串存进去的时候,该树只有一个 root 节点。
比如说我们第一个要把
再比如说,我们第一个要把
当然,当一些题目需要询问存进去了哪些串时,需要加一个标记或者计数数组(这取决于该题是否要给它们计数),当存完了时打上标记即可。
这样,一棵 Trie 树就建好了,建树的时间复杂度为:
3.Trie的查询
Trie的查询很好懂。
比如说刚才图片中的树,我们要查询
可以发现,root 节点有一条边连着
以此类推,直到我们搜完
再比如我们要搜索
搜
是不是发现建树和搜树差不多?于是就有了一些边建边搜的题。
4.Trie的代码实现
相信你如果读懂了 Trie 建树搜树的过程,一定能自己手打了把。当然不会的也没关系,看这里:
建树:
void build(string s){
int pos=0,len=s.size();
for(int i=0;i<len;i++){
if(!nxt[pos][s[i]-'a']){
nxt[pos][s[i]-'a']=++cnt;
}
pos=nxt[pos][s[i]-'a'];
}
f[pos]=true;
}
搜树:
int find(string s){
int pos=0,len=s.size();
for(int i=0;i<len;i++){
if(!nxt[pos][s[i]-'a'])return -1;
pos=nxt[pos][s[i]-'a'];
}
if(!f[pos])return -1;
return 1;
}
5.Trie的例题
例题1:于是他错误的点名开始了
题目大意:给你 WRONG。如果之前询问点过名,输出 REPEAT。否则输出 OK。
虽然这题用 map 也可以 AC,但是这也是一道非常适合 Trie 的板子题。
思路:我们维护一个前缀树,再用一个标记数来统计是否出现过就可以了。
代码:
#include<bits/stdc++.h>
#define code return
#define by 0
#define doooge ;
using namespace std;
int nxt[500010][30],cnt;
bool f[500010],f2[500010];//判断是否出现过
void build(string s){
int pos=0,len=s.size();
for(int i=0;i<len;i++){
if(!nxt[pos][s[i]-'a']){
nxt[pos][s[i]-'a']=++cnt;
}
pos=nxt[pos][s[i]-'a'];
}
f[pos]=true;
}
int find(string s){
int pos=0,len=s.size();
for(int i=0;i<len;i++){
if(!nxt[pos][s[i]-'a'])return -1;
pos=nxt[pos][s[i]-'a'];
}
if(!f[pos])return -1;
if(f2[pos])return 0;
f2[pos]=true;
return 1;
}
int main(){
int n,m;
cin>>n;
for(int i=1;i<=n;i++){
string s;
cin>>s;
build(s);
}
cin>>m;
while(m--){
string s;
cin>>s;
int res=find(s);
if(res==-1)cout<<"WRONG\n";
else if(res==0)cout<<"REPEAT\n";
else cout<<"OK\n";
}
code by doooge
}
例题2:【模板】字典树
思路:这题与我们将的 Trie 有所不同的是他需要计数有多少个子串,这时我们就可以用一个计数数组来代替标记数组就可以了。注意判断大小写。
注意初始化别用 memset,特别慢。
代码:
#include<bits/stdc++.h>
#define code return
#define by 0
#define doooge ;
using namespace std;
int nxt[3000010][70],cnt;
int sum[3000010];
int get(char c){
if(c>='0'&&c<='9')return 52+c-'0';
return (c<'a'?26+c-'A':c-'a');
}
void build(string s){
int pos=0,len=s.size();
for(int i=0;i<len;i++){
if(!nxt[pos][get(s[i])]){
nxt[pos][get(s[i])]=++cnt;
}
pos=nxt[pos][get(s[i])];
sum[pos]++;
}
}
int find(string s){
int pos=0,len=s.size();
for(int i=0;i<len;i++){
if(!nxt[pos][get(s[i])])return 0;
pos=nxt[pos][get(s[i])];
}
return sum[pos];
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
cin>>t;
while(t--){
for(int i=0;i<=cnt;i++){
for(int j=0;j<=66;j++)nxt[i][j]=0;
}
for(int i=1;i<=cnt;i++)sum[i]=0;
cnt=0;
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
string s;
cin>>s;
build(s);
}
while(m--){
string s;
cin>>s;
cout<<find(s)<<'\n';
}
}
code by doooge
}
6.作业
作业T1:
阅读理解,难度:
作业T2:
前缀统计,难度:
作业T3:
会议座位,难度:
7.闲话
下一篇文章将讲 Trie的变种:01-Trie和Trie的合并,还有可持久化Trie,期待一下!
蒟蒻不才,膜拜大佬。如果文章错了字或代码,请在评论区@我。