详解Trie
一.Trie的概念
Trie又称字典树,前缀树(事实上前缀树这个名字就很好的解释了Trie的储存方式)
来一张图理解一下Trie的储存方式:(图片来自百度百科)

由这张图我们也可以知道Trie的特点:
- Trie的根节点是空的
- 除根节点外,每个节点储存一个单词/字母
- 也就是说,从根节点到每个单词节点的路径上的所有字母连接而成的字符串就是该节点对应的字符串
- 每个非叶子结点一般都会被多次使用,以节省遍历时的时间效率
- 另外,每个节点下面的数字是他们的编号,这个具体在下面再展开
所以读者们不难看出,
Trie是典型的用空间来换时间的做法
二.Trie的操作
Trie的常用操作:插入,查询,删除
(插入,查询为常用操作,删除操作实在少见就不展开讲了)
在这之前我们需要填一下上面的坑:
那些非根节点下面的数字是什么意思?
事实上,在代码中,我们一般把根节点编号为0,然后把其余节点从1开始编号,然后存在一个数组中(数组的用处是储存每个节点的所有子节点,以便于直接使用下标来存取)
再具体一点,在我的代码中,用ch[i][j]来保存i的那个编号为j的子节点,当然,如果ch[i][j]==0,也就代表这个节点不存在,即i没有这个编号为j的子节点
好了,坑填完了
我们来明确一下另外一个数组的概念:val数组
对于需要用到Trie的题目,一般是不会只让你单纯构建一棵Trie的,一般都会有各种奇奇怪怪的要求,就像我们的例题(例题在下面),怎么处理这一些要求?我们可以把他们看作是一些“附加信息”,然后储存起来,val数组就是用来储存附加信息的
那么现在我们就来讲一下插入操作(具体解释都在代码中):
(我的代码一般是没有指针这种不是很友好的东西的,不过这里因为是定义在结构体里面的,比较麻烦,所以干脆写个指针,意思很好懂的,代码中有注解)
#define ll int
#define N 500010
struct Trie{
ll ch[N][],sz,val[N];
//val为附加信息
//这里的ch数组,第二维的大小为26是因为字符串只由小写字母构成,第二维的大小一般是由字符串的组成决定
//sz即为节点编号
Trie(){
sz=;//一开始的时候只有根节点这一个节点
memset(ch[],,sizeof(ch[]));
memset(val,,sizeof(val));
}//这里是初始化
ll idx(char c){return c-'a';}//返回字符c的编号
void insert(char *s,ll v){
//插入操作 ,这里是整份代码中唯一用到指针的地方,因为函数是放在结构体里面 ,所以最好用个指针,其实等价于char s[]
//s代表要插入的字符串,v为附加信息
ll u=,len=strlen(s+);
for(ll i=;i<=len;i++){
ll c=idx(s[i]);
if(!ch[u][c]){//如果节点不存在就插入,不然就继续往下遍历
memset(ch[sz],,sizeof(ch[sz]));
val[sz]=;//中间的节点是没有附加信息的
ch[u][c]=sz++;//新建节点
}
u=ch[u][c];//往下遍历
}
val[u]=v;//插入附加信息,注意,我们一般只在叶子节点插入附加信息,中间的节点一般是没有附加信息的,因为一个非叶子结点,在Trie中一般都会被不同的单词使用到(定义)
}
}tree;
接下来是查找操作,事实上查找操作基本上是和插入操作是差不多的,如果你真的理解了上面的insert操作,可以尝试着在不看我下面代码的情况下去自己写一下,同样,对于这个操作的解释放在代码中
ll search(char *s){//这里的指针意义同上
//对于这一个查找操作,我们就以最简单的操作来展开:
//查找这个字符串是否在Trie中出现过,如果出现过,返回它的附加信息
ll u=,len=strlen(s+);
for(ll i=;i<=len;i++){
ll c=idx(s[i]);
if(!ch[u][c])return -;
//没有这个节点,返回-1,即代表这个字符串没有在Trie中出现过
//这个返回值得看题目所需要的附加信息来决定,因为这里是不可以与附加信息冲突的,在这里我们假定附加信息为0或正数
u=ch[u][c];//继续向下遍历
}
return val[u];//这个节点存在,返回它的附加信息
}
三.Trie的题目
1.模板题:luogu P2580 于是他错误的点名开始了
题面篇幅有点长,就不贴了,大概讲一下题意:
给你n个字符串以及m个询问,每次询问需要回答:如果该询问的字符串没有在n个字符串里面出现过,输出“WRONG”,如果该询问的字符串出现过而且是第一次询问,输出“OK”,如果该询问的字符串出现过但不是第一次询问,输出“REPEAT”
仔细看一下题目,事实上就是字典树的模板题,对于insert操作和search操作进行一下微调就可以了
主要的调整是,附加信息不在insert操作里面插入,而是在search操作中插入
(在本题中,附加信息为该字符串是不是第一次被询问)
剩下的跟上面的代码内容都差不多了,接下来上代码
#include <cstdio>
#include <cstring>
#define ll int
#define inf 1<<30
#define mt(x,y) memset(x,y,sizeof(x))
#define il inline
il ll max(ll x,ll y){return x>y?x:y;}
il ll min(ll x,ll y){return x<y?x:y;}
il ll abs(ll x){return x>?x:-x;}
il void swap(ll &x,ll &y){ll t=x;x=y;y=t;}
il void read(ll &x){//快读
x=;ll f=;char c=getchar();
while(c<''||c>''){if(c=='-')f=-f;c=getchar();}
while(c>=''&&c<=''){x=x*+c-'';c=getchar();}
x*=f;
}
using namespace std;
#define N 500010
struct Trie{
ll val[N],ch[N][],sz;
Trie(){
sz=;
memset(ch[],,sizeof(ch[]));
memset(val,,sizeof(val));
}//初始化
ll idx(char c){return c-'a';}
void insert(char *s){
ll u=,len=strlen(s+);
for(ll i=;i<=len;i++){
ll c=idx(s[i]);
if(!ch[u][c]){
memset(ch[sz],,sizeof(ch[sz]));
// val[sz]=0; 这里并不需要这么标记中间信息,因为在初始化那里已经将所有的附加信息初始化为0了
ch[u][c]=sz++;
}
u=ch[u][c];
}
}
ll search(char *s){
ll u=,len=strlen(s+);
for(ll i=;i<=len;i++){
ll c=idx(s[i]);
if(!ch[u][c])return ;
//如果没有这个节点,也就意味着询问的这个字符串不存在,输出WRONG
u=ch[u][c];
}
if(!val[u]){
val[u]=;return ;//这个字符串已经询问过了,标识一下,然后输出OK
}
return ;//这个字符串正确,但不是第一次出现,输出REPEAT
}
}tree;
ll n,m;
int main(){
read(n);char s[];
for(ll i=;i<=n;i++){
scanf("%s",s+);
tree.insert(s);
}
read(m);
for(ll i=;i<=m;i++){
scanf("%s",s+);
ll pd=tree.search(s);
if(pd==)printf("WRONG\n");
else if(pd==)printf("OK\n");
else if(pd==)printf("REPEAT\n");
}
return ;
}
2.UVA11732 "strcmp()" Anyone?(这里给的是洛谷的链接,国内进uva比较慢)
update:2018.5.22
给一点提示:
1.注意要用左儿子右兄弟法储存
2.题目中的“比较次数”说的有点迷,这里阐述一下
相同长度为L的两个单词比较代价为2L-1,除了最后一次外要进行s结束的判断;
单词的比较长度应该为strlen(s)+1,结束符也是比较的一环;
如果两个单词相同,则多计算一次结束判断,比较次数+1。
这里就不给代码了(个人感觉这道题独立思考会更好一些,挺经典的一道题了,更主要是因为我这道题的代码打的比较丑)
至于翻译网上找找就有的
3.[AHOI2005]病毒检测
update:2018.6.2
做法:Trie+dfs/bfs,也可以尝试用dp写
之后会搞一篇这道题的题解,坑先留着
四.总结
上面的题目打过一遍之后基本就可以把Trie掌握了,注:会不定时更新题目
Trie其实不算是多难的数据结构,算是字符串里面一个比较基本的东西吧,然后就是推荐如果学习完Trie可以把hash和kmp也顺便给学了,当然,如果你Trie和kmp都吃透了就可以尝试挑战一下AC自动机了!
转载请注明出处:http://www.cnblogs.com/henry-1202/p/9029822.html
详解Trie的更多相关文章
- trie字典树详解及应用
原文链接 http://www.cnblogs.com/freewater/archive/2012/09/11/2680480.html Trie树详解及其应用 一.知识简介 ...
- trie树--详解
文章作者:yx_th000 文章来源:Cherish_yimi (http://www.cnblogs.com/cherish_yimi/) 转载请注明,谢谢合作.关键词:trie trie树 数据结 ...
- 转:trie树--详解
前几天学习了并查集和trie树,这里总结一下trie. 本文讨论一棵最简单的trie树,基于英文26个字母组成的字符串,讨论插入字符串.判断前缀是否存在.查找字符串等基本操作:至于trie树的删除单个 ...
- 字典树(Trie)详解
详解字典树(Trie) 本篇随笔简单讲解一下信息学奥林匹克竞赛中的较为常用的数据结构--字典树.字典树也叫Trie树.前缀树.顾名思义,它是一种针对字符串进行维护的数据结构.并且,它的用途超级广泛.建 ...
- [转] AC自动机详解
转载自:http://hi.baidu.com/nialv7/item/ce1ce015d44a6ba7feded52d AC自动机详解 AC自动机是用来处理多串匹配问题的,即给你很多串,再给你一篇文 ...
- 【转】AC算法详解
原文转自:http://blog.csdn.net/joylnwang/article/details/6793192 AC算法是Alfred V.Aho(<编译原理>(龙书)的作者),和 ...
- 以太坊客户端Geth命令用法-参数详解
Geth在以太坊智能合约开发中最常用的工具(必备开发工具),一个多用途的命令行工具. 熟悉Geth可以让我们有更好的效率,大家可收藏起来作为Geth命令用法手册. 本文主要是对geth help的翻译 ...
- HanLP中人名识别分析详解
HanLP中人名识别分析详解 在看源码之前,先看几遍论文<基于角色标注的中国人名自动识别研究> 关于命名识别的一些问题,可参考下列一些issue: l ·名字识别的问题 #387 l ·机 ...
- HanLP中的人名识别分析详解
在看源码之前,先看几遍论文<基于角色标注的中国人名自动识别研究> 关于命名识别的一些问题,可参考下列一些issue: u u名字识别的问题 #387 u u机构名识别错误 u u关于层叠H ...
随机推荐
- C语言实现万年历
给出你想知道的年份,便可以计算出该年对应的每个月每个日所对应的星期数,是不是感觉很好玩 ? #include <stdio.h> #include<stdlib.h> long ...
- JQuery实战总结三 标签页效果图实现
在浏览网站时我们会看到当我们鼠标移到多个选项卡上时,不同的选项卡会出现自己对应的界面的要求,在同一个界面上表达了尽量多的信息.大大额提高了空间的利用率.界面的切换效果也是不错的哦,这次自己可以实现啦. ...
- Java进阶(二十二)使用FileOutputStream写入文件
Java使用FileOutputStream写入文件 绪 在Java中,文件输出流是一种用于处理原始二进制数据的字节流类.为了将数据写入到文件中,必须将数据转换为字节,并保存到文件.请参阅下面的完整的 ...
- 网站开发进阶(二十三)Address already in use: JVM_Bind <null>:8088
Address already in use: JVM_Bind <null>:8088 注:请点击此处进行充电! 阿里云服务器又莫名其妙的宕掉!内存泄漏问题依然存在,又出现了端口占用的情 ...
- TCP的核心系列 — SACK和DSACK的实现(三)
不论是18版,还是37版,一开始都会从TCP的控制块中取出SACK选项的起始地址. SACK选项的起始地址是保存在tcp_skb_cb结构的sacked项中的,那么这是在什么时候做的呢? SACK块并 ...
- OpenCV图片矩阵操作相关,对png图片操作(多通道)
文献链接: http://www.cnblogs.com/tornadomeet/archive/2012/12/26/2834336.html 下面这个高手,写了个小程序我还没有调试,回头 调试看看 ...
- 如何在os x或ubuntu下安装最新的ruby
os x下基本上可以安装到比较新的ruby,首先先安装rvm,然后用rvm list known看当前可供安装的ruby的版本,不过这也不是绝对的,比如在我的os x 10.9上,命令返回如下: # ...
- Android 客户端与服务器交互
在android中有时候我们不需要用到本机的SQLite数据库提供数据,更多的时候是从网络上获取数据,那么Android怎么从服务器端获取数据呢?有很多种,归纳起来有 一:基于Http协议获取数据方法 ...
- ES6之let命令
ES6新增了let命令,用来声明变量.它的用法类似于var. let和var声明变量的区别: 1.let声明的变量,只在let命令所在的代码块内有效,出了这个块级作用域就不起作用 先看一个例子: { ...
- IIS服务器如何抗住高并发的客户端访问
今天被问到一个问题,如果你在阿里云上部署了一个IIS服务器,此时如果有成千上万的客户端来访问,你将如何设计?我东扯扯西谈谈,说加个线程池来处理,在加个请求队列.当时觉得说的没有问题,现在想想,服务器自 ...