Trie树简单讲解
trie树简单讲解
一.Trie树是什么
Trie树如下
trie树就是一棵树,根节点什么都不存,子节点存的不是数字,而是字母
如图所示,图中有三个单词:me,hi,how.
trie树可以方便地查找这些单词,也可以找出某个前缀出现的次数
二.如何建树
我们可以开一个数组
trie[i][j]=k
它的意思是编号为i的节点有一个编号为j的儿子,并且它代表的字母是k
我们用数字代表字母,那么a=0,b=1,c=2.....以此类推,k就是这些数字
三.插入单词
假设在这棵树中我们要插入一个单词
这样有两种情况:
1.它的某个字母已存在树中
2.它的某个字母不存在树中
于是我们举一个例子:
插入“house”
我们首先在根节点的儿子中找“h”,发现找到了,属于第一种情况
于是我们继续在“h”的儿子中找“o”,也找到了,还是属于第一种情况
接下来我们在“o”的儿子中找“u”,没有找到,属于第二种情况,这时候我们要再建一个“o”的子节点
...................(以此类推)
于是就插入完了:
具体代码如下:
void input(char a[12],int rt)//a字符串是要插入的字符串,rt是root,当前根节点编号 { int len=strlen(a);//取a的长度 for(int i=0;i<=len-1;i++)//枚举a中每一个字符 { int x=a[i]-'a';//当前字母的数字编号 if(trie[rt][x]==0)//第二种情况,及树中不存在这个点 (第一种情况不用处理,直接看下一个) { trie[rt][x]=++idx;//新建一个点(idx是访问顺序) } rt=trie[rt][x]; // 将根节点改为当前点,以便下次查找下一个点 } }
四.查找单词
我们以查找前缀举例讲解:
假设我们查找前缀“mou”
首先我们在根节点的儿子m与h中找“mou”的首字母“m”,发现找到了
我们接下来在“m”的子节点中找“o”,发现找不到,于是我们可以直接断定找不到这个前缀啦
具体代码如下:
int search(char a[12],int rt) { int len=strlen(a); for(int i=0;i<=len-1;i++) { int x=a[i]-'a'; if(trie[rt][x]==0)//如果不存在这个点 { return 0;//直接返回 0(找不到) } rt=trie[rt][x];//更新root } return 1;//如果for循环结束了还没有返回,既没有找不到,返回找得到 }
五.完整代码
第一种情况:查找前缀是否存在
#include <cstdio> #include <cstring> #include <iostream> using namespace std; const int maxn=10000000; int idx=0,trie[maxn][26],root,ans,cnt[maxn]; char s[12]; void input(char a[12],int rt)//a字符串是要插入的字符串,rt是root,当前根节点编号 { int len=strlen(a);//取a的长度 for(int i=0;i<=len-1;i++)//枚举a中每一个字符 { int x=a[i]-'a';//当前字母的数字编号 if(trie[rt][x]==0)//第二种情况,及树中不存在这个点 (第一种情况不用处理,直接顺着走) { trie[rt][x]=++idx;//新建一个点(idx是访问顺序) } rt=trie[rt][x]; // 将根节点改为当前点,以便下次查找下一个点(顺着走) } } int search(char a[12],int rt) { int len=strlen(a); for(int i=0;i<=len-1;i++) { int x=a[i]-'a'; if(trie[rt][x]==0)//如果不存在这个点 { return 0;//直接返回 0(找不到) } rt=trie[rt][x];//更新root } return 1;//如果for循环结束了还没有返回,既没有找不到,返回找得到 } int main() { root=0; while(gets(s) && strcmp(s,"")!=0)//一直输入若干字符串,一直到此行为空 { input(s,root); } while(gets(s) && strcmp(s,"")!=0)//一直输入若干字符串,一直到此行为空 { if(search(s,root)==1) cout<<"yes"; else cout<<"no"; } }
第二种情况:求以该字符串为前缀的单词的数量(详细方法见注释)
#include <cstdio> #include <cstring> #include <iostream> using namespace std; const int maxn=10000000; int idx=0,trie[maxn][26],root,ans,cnt[maxn]; char s[12]; void input(char a[12],int rt) { int len=strlen(a); for(int i=0;i<=len-1;i++) { int x=a[i]-'a'; if(trie[rt][x]==0) { trie[rt][x]=++idx; } cnt[trie[rt][x]]++;//查询前缀出现次数用的,即包含这个前缀的单词加一 rt=trie[rt][x]; } } void search(char a[12],int rt) { int len=strlen(a); for(int i=0;i<=len-1;i++) { int x=a[i]-'a'; if(trie[rt][x]==0) { ans=0; //前缀出现的次数为零(没有这个前缀) return; } rt=trie[rt][x]; } ans=cnt[rt];//有这个前缀的单词数 return; } int main() { root=0; while(gets(s) && strcmp(s,"")!=0) { input(s,root); } while(gets(s) && strcmp(s,"")!=0) { search(s,root); cout<<ans<<endl;//输出有这个前缀的单词数 } }
第三种情况:求单词是否出现过(详细方法见注释)
#include <cstdio> #include <cstring> #include <iostream> using namespace std; const int maxn=10000000; int idx=0,trie[maxn][26],root,ans,cnt[maxn]; char s[12]; int exist[maxn]={0};//标记单词是否存在的数组 void input(char a[12],int rt) { int len=strlen(a); for(int i=0;i<=len-1;i++) { int x=a[i]-'a'; if(trie[rt][x]==0) { trie[rt][x]=++idx; } rt=trie[rt][x]; } exist[rt]=1;//这个单词存在(要标记在词尾,以保证整个单词都存在) } int search(char a[12],int rt) { int len=strlen(a); for(int i=0;i<=len-1;i++) { int x=a[i]-'a'; if(trie[rt][x]==0) { return 0; } rt=trie[rt][x]; } return exist[rt];//返回是否存在 } int main() { root=0; while(gets(s) && strcmp(s,"")!=0) { input(s,root); } while(gets(s) && strcmp(s,"")!=0) { if(search(s,root)==1) cout<<"yes"; else cout<<"no"; } }
六.模板题
1.hdu 1251 统计难题:http://acm.hdu.edu.cn/showproblem.php?pid=1251
2.洛谷 P2580 于是他错误的点名开始了: https://www.luogu.com.cn/problem/P2580