Trie树(字典树)
一、简介
字典树,也称 Trie、字母树,指的是某个字符串集合对应的形如下图的有根树。树的每条边上对应有恰好一个字符,每个顶点代表从根到该节点的路径所对应的字符串(将所有经过的边上的字符按顺序连接起来)。有时我们也称 Trie 上的边为转移,顶点为状态。顶点上还能存储额外的信息,例如下图中双圈圈表示顶点所代表的字符串是实际字符串集合中的元素。而实际字符串中的元素都会对应 Trie 中某个顶点代表的串。实际上,任意一个节点所代表的字符串,都是实际字符串集合中某些串的前缀,特别地,根节点表示空串。此外,对于任意一个节点,它到它儿子节点边上的字符都互不相同。可以发现,Trie 很好的利用了串的公共前缀,节约了存储空间。若将字符集看做是小写英文字母,则 Trie 也可以看做是一个 26 叉树,在插入询问新字符串时与树一样,找到对应的边往下走。
二、操作实现
字典树主要有两种操作,插入和查询,这两种操作都非常简单,用一个一重循环均可解决,即第 i 次循环时找到前 i 个字符所对应的节点,然后进行相应的操作。Trie 的节点可以使用一个结构体进行存储,如下代码中,trans[i]表示这个节点边上字符为i 的转移边所到达的儿子节点编号,若为 0 则表示没有这个儿子。
struct node{
int trans[Z];// Z 为字符集大小
bool bo;// 若 bo=true 则表示这个顶点代表的字符串是实际字符串集合中的元素
}tr[N];
现在要对一个字符集为小写英文字母的 Trie 插入一个字符串 S:
void insert(char *s) {
int len = strlen(s);
int u = 1; // 1 为根节点
for (int i = 0; i < len; ++i) {
if (!tr[u].trans[s[i] - 'a']) // 若不存在这条边则要新建一个节点与转移边
tr[u].trans[s[i] - 'a'] = ++tot; // tot 为总点数
u = tr[u].trans[s[i] - 'a'];
}
tr[u].bo = true; // 在串的结尾处将 bo 赋值,表示它代表一个实际字符串集合中的元素
}
查询一个字符串 S 是否是给定字符串集合中某个串的前缀:
bool insert(char *s) {
int len = strlen(s);
int u = 1;
for (int i = 0; i < len; ++i) {
if (!tr[u].trans[s[i] - 'a']) return false;
u = tr[u].trans[s[i] - 'a'];
}
return true;
}
将一个字符串集合构成一棵\(Trie\)的复杂度为\(O(\sum L)\),\(\sum L\)为所有字符串的总长。查询一个串\(S\)的时间复杂度则为\(O(|\)S\(|)\)。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int T,n,cnt;
int ch[50007][17];
bool bo[50007];
char s[21];
void clear(){
memset(ch,0,sizeof(ch));
memset(bo,false,sizeof(bo));
}
bool insert (char * s){
int len=strlen(s);
int u=1;
bool flag=false;
for(int i=0;i<len;++i){
int c=s[i]-'0';
if(!ch[u][c])
ch[u][c]=++cnt;
else if(i==len-1)
flag=true;
u=ch[u][c];
if(bo[u]) flag=true;
}
bo[u]=true;
return flag;
}
int main(){
freopen("in.in","r",stdin);
freopen("out.out","w",stdout);
scanf("%d",&T);
while(T--){
scanf("%d",&n);
cnt=1;
clear();
bool ans=false;
for(int i=1;i<=n;++i){
scanf("%s",s);
if(insert(s)) ans=true;
}
if(!ans) puts("YES");
else puts("NO");
}
return 0;
}
浙公网安备 33010602011771号