Trie树简单讲解
字典树(Trie Tree)
trie树就是用来对于大量的文本统计,储存与查找的一种数据结构,下面简单介绍两种trie树的实现方式。
- 多叉trie树
对于一个字符集大小为的文本,我们可以建立一棵叉树,将文本统计。
具体实现如下:
首先我们对于一个节点,开一个大小为的数组,来表示它对于的子节点。比如对于小写字母的字符集,大小为,我们就可以定义如下的节点:(我们将~重新编号为~)
struct node{
int son[26];
int flag;
};
其中,表示当前这个节点下一个字母编号为所对应的节点的编号,我们是将字母放在边上的。
所以对于一个字符串,我们可以用的时间插入到trie树中,为字符串长度。
int tot;//表示节点个数
node trie[N];/trie树节点
int getid(char c){return c-'a';}
//获取字符编号
void insert(char *s,int len){
int nowid=0;//当前的节点编号,这里我们把0号点当做根节点。
for(int i=0,j;i<len;i++){
j=getid(s[i]);//获取当前的字符编号
if(!trie[nowid].son[j])
trie[nowid].son[j]=++tot;//如果没有该结点,那么就将新建一个节点,节点个数增加。
nowid=trie[nowid].son[j];
//走到下一个节点。
}
trie[nowid].flag++;
//将结束位置做上标记,表示有一个字符串在这里结束。
}
而我们发现,当字符集很大时,空间复杂度最坏是的,所以有没有更好的方法呢?下面就来介绍第二种。
- 链表式trie树(时间换空间法)
我们可以像建图一样,将trie树建出,而没出现的节点就可以不用建,那么将大大节省空间复杂度,但是也有坏处就是我们不能的查找某一个节点对应的下一个某个字母的节点编号,而是需要去遍历所以的子节点,但是有时候时间换空间是明智的。
具体实现是类似的,但是在这里我们就不定义节点,可以定义一个边,如下:
struct side{
int to,next;
char c;//记录每条边对应的字符
};
int head[M],cnt;
//to表示下一个节点的编号和next表示链表的指针。
//head表示链表的表头,cnt记录边数
那么增加一条边就简单了:
side g[N];
void add(int a,int b,char c){
g[++cnt]=(side){b,head[a],c};head[a]=cnt;
}
然后插入一个字符串的方式同第一种,只是细节变化了一点。
int flag[N];//记录节点标记
int tot;//表示节点个数
void insert(char *s,int len){
int nowid=0,pos;//同样的0为根节点,pos记录下一个边的编号。
for(int i=0;i<len;i++){
for(pos=head[nowid];pos;pos=g[e].next){
if(g[pos].c==s[i])break;
}//去寻找下一个节点的编号
if(!pos){
add(nowid,++tot,s[i]);
pos=cnt;
}
nowid=g[pos].to;
}
flag[nowid]++;
}
这就是两种trie树的方法啦!QAQ有错请指出
例题:
HDU4117. GRE Words
HDU2296. Ring
HDU2243. 考研路茫茫——单词情结
HDU2457. DNA repair
POJ2778. DNA Sequence
POJ1204. Word Puzzles
HDU3065. 病毒侵袭持续中
ZOJ3430. Detect the Virus
HDU2222. Keywords Search
BZOJ2938. 病毒
应用技巧:
其实trie树大多都是运用在AC自动机上面,然后夹杂一些其他的例如DP之类的。
trie树的特殊应用:可以来求异或最大值,方法就是将所有数字转换成二进制插入trie树,这时的trie树就变成了一个二叉树,然后异或最大就按照高位到低位贪心的在trie树上找一遍就好了。
下面附上一个AC自动机学习和trie树学习的神器
Go To Download密码xbj6