一.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的更多相关文章

  1. trie字典树详解及应用

    原文链接    http://www.cnblogs.com/freewater/archive/2012/09/11/2680480.html Trie树详解及其应用   一.知识简介        ...

  2. trie树--详解

    文章作者:yx_th000 文章来源:Cherish_yimi (http://www.cnblogs.com/cherish_yimi/) 转载请注明,谢谢合作.关键词:trie trie树 数据结 ...

  3. 转:trie树--详解

    前几天学习了并查集和trie树,这里总结一下trie. 本文讨论一棵最简单的trie树,基于英文26个字母组成的字符串,讨论插入字符串.判断前缀是否存在.查找字符串等基本操作:至于trie树的删除单个 ...

  4. 字典树(Trie)详解

    详解字典树(Trie) 本篇随笔简单讲解一下信息学奥林匹克竞赛中的较为常用的数据结构--字典树.字典树也叫Trie树.前缀树.顾名思义,它是一种针对字符串进行维护的数据结构.并且,它的用途超级广泛.建 ...

  5. [转] AC自动机详解

    转载自:http://hi.baidu.com/nialv7/item/ce1ce015d44a6ba7feded52d AC自动机详解 AC自动机是用来处理多串匹配问题的,即给你很多串,再给你一篇文 ...

  6. 【转】AC算法详解

    原文转自:http://blog.csdn.net/joylnwang/article/details/6793192 AC算法是Alfred V.Aho(<编译原理>(龙书)的作者),和 ...

  7. 以太坊客户端Geth命令用法-参数详解

    Geth在以太坊智能合约开发中最常用的工具(必备开发工具),一个多用途的命令行工具. 熟悉Geth可以让我们有更好的效率,大家可收藏起来作为Geth命令用法手册. 本文主要是对geth help的翻译 ...

  8. HanLP中人名识别分析详解

    HanLP中人名识别分析详解 在看源码之前,先看几遍论文<基于角色标注的中国人名自动识别研究> 关于命名识别的一些问题,可参考下列一些issue: l ·名字识别的问题 #387 l ·机 ...

  9. HanLP中的人名识别分析详解

    在看源码之前,先看几遍论文<基于角色标注的中国人名自动识别研究> 关于命名识别的一些问题,可参考下列一些issue: u u名字识别的问题 #387 u u机构名识别错误 u u关于层叠H ...

随机推荐

  1. Python学习 - 输入和输出

    #输出 print('hello, python') print('The quick brown fox', 'jumps over', 'the lazy dog') #多个字符串,用逗号隔开,就 ...

  2. python爬虫 - python requests网络请求简洁之道

    http://blog.csdn.net/pipisorry/article/details/48086195 requests简介 requests是一个很实用的Python HTTP客户端库,编写 ...

  3. Rust语言之HelloWorld

    Rust语言之HelloWorld 参考文档: http://doc.crates.io/guide.html 1 什么是Cargo 相当于maven/ant之于java, automake之于c, ...

  4. Dynamics CRM 2011/2013 通过Javascript给lookup字段赋值

    仅仅做下记录,因为老是用到但老是忘记 var value = new Array(); value[0] = new Object(); value[0].id = idValue; value[0] ...

  5. Dynamics CRM2011 隐藏sub-grid 新建项和添加现有项按钮

    在CRM2011中ribbon区的自定义按钮可以通过工具例如RibbonEditor或者RibbonWorkbench进行配置包括action.display等等,但是系统级别的按钮是不能进行编辑的, ...

  6. Unity3D学习笔记(三)Unity的C#基础

    在C#脚本中,必须显式的继承MonoBehaviour类需要注意的是,在创建C#脚本时,脚本名应尽量符合C#命名规则,以字母或下划线开头,因为类名的默认跟随脚本名.C#声明变量的方式和C++和Java ...

  7. Linux用户配置文件(第二版)

    /etc/passwd文件剖析 文件格式: root:x:0:0:root:/root:/bin/bash 用户名:密码位:UID:GID[缺省组ID]:注释性的描述信息:宿主目录:shell[7部分 ...

  8. Android Bootloader LittleKernel的两篇文章

    Android 开发之 ---- bootloader (LK) LK是什么 LK 是 Little Kernel 它是 appsbl (Applications ARM Boot Loader)流程 ...

  9. iOS中NSBundle的介绍

    bundle是一个目录,其中包含了程序会使用到的资源.这些资源包含了如图像,声音,编译好的代码,nib文件(用户也会把bundle称为plug-in).对应bundle,cocoa提供了类NSBund ...

  10. 服务端技术进阶(二)JBoss和tomcat的区别

    JBoss和tomcat的区别 注意JBoss和tomcat是不一样,JBoss是一个可伸缩的服务器平台,当你的EJB程序编制完成后,如果访问量增加,只要通过增加服务器硬件就可以实现多台服务器同时运算 ...