【镇楼】

  不满足于粗浅的表面了解字典树吗,今天!由我给大家带来!字典树与01字典树的解析!!

目录

【引入】

【字典树】

【01字典树】

【引用参考】


【引入】

字典是干啥的?查找字的。那么字典树顾名思义,自然也就是起查找作用的一种树,查找的是啥?单词。

我们先来看以下两个问题:

1、给出n个单词和m个询问,每次询问一个单词,回答这个单词是否在单词表中出现过。

答:用map记录即可,短小精悍。

2、给出n个单词和m个询问,每次询问一个前缀,回答询问是多少个单词的前缀。

答:map,把每个单词拆开。

judge:n<=200000,TLE!

这就需要一种高级数据结构——Trie树(字典树)

【字典树】

字典树显然是一棵树,那么如何建树呢?树的边表示什么呢?结点又表示什么呢?

【建树】

我们对 cat,cash,app,apple,aply,ok 建一颗字典树,建成之后如下图一所示。

从图中可以看出,执行的操作是:从左到右扫这个单词,如果字母在相应根节点下没有出现过,就插入这个字母;否则沿着字典树往下走,直到单词的下一个字母没有出现过或者遍历结束。

          

这就产生一个问题:往哪儿插?我们需要给它指定一个位置,那就需要给每个字母编号。

【插入】

我们设数组trie[i][j]=k,表示编号为i的节点的第j个孩子是编号为k的节点。

什么意思呢?这里有2种编号,一种是i,k表示节点的位置编号,这是相对于整棵树而言的;另一种是j,表示节点i的第j的孩子,这是相对于节点i而言的。请结合图二理解。

还有一种编号方式,因为每个节点最多有26个子节点,我们可以按他们的字典序从0-25编号。trie[i][j]=true|false,表示编号为i的节点的第j个孩子是否已经存在。请结合图三

通过上面的图我们可以清晰的看到:

1.字典树的边表示字母。

2.字典树的结点用于存放一个特殊字符,记录从根节点到这个节点为止这样一个单词。

3.有相同前缀的单词共用前缀结点,所以我们可以快速跑出最长公共前缀、最多单词的公共前缀等等。

4.第二种编号方式是由单词字母所在的位置即深度和字母的字典序所决定的,每个节点的子节点都应该从0编到25,会造成比较大的空间浪费。而第一种编号方式显然比较适用于大部分情况,节约空间,用到哪个分哪个。

【查找】

从左往右依次扫描每个字母,顺着字典树往下找,能找到这个字母,往下走,否则结束查找,即没有这个前缀;前缀扫完则表示有这个前缀。可以查找前缀、单词是否出现过,或者查找前缀出现的次数(开一个数组sum[]存储),查找某个单词等等。

【例题】

下面让我们结合题目来理解代码实现:

以 hdu 1251 统计难题 为例

题意:给定一些单词,再给出一些前缀询问这些前缀出现次数?

代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
int sum[maxn]={0};
int trie[maxn][26];
char s[15];
int pos=1;
void add() //插入
{int c=0;for(int i=0;s[i];i++){int x=s[i]-'0';if(trie[c][x]==0) //没出现过就增加一个结点trie[c][x]=pos++;c=trie[c][x]; //下一个结点sum[c]++; //更新前缀出现次数}
}
void query() //查找
{int c=0;for(int i=0;s[i];i++){int x=s[i]-'0';if(trie[c][x]==0){ //找不到说明没出现过printf("0\n");return;}c=trie[c][x]; //下一个结点}printf("%d\n",sum[c]);
}
int main()
{while(gets(s)&&s[0]!=NULL)add();while(gets(s))query();return 0;
}

【01字典树】

01字典树主要用于解决求异或最值的问题。

01字典树和普通的字典树原理类似,只不过把插入字符改成了插入二进制串的每一位(0或1)。

让我们通过一段简短的代码理解一下实现过程。

int pos=0;
void add(int num) //插入
{int c=0;for(int i=31;i>=0;i--){int op=((num>>i)&1);if(!trie[c][op])trie[c][op]=++pos;c=trie[c][op];val[c]++;}val[c]=num; //节点值为x,到这里是一个数
}
int query(int num)
{ //查询所有数中和num异或结果最大的数int c=0;for(int i=31;i>=0;i--){int op=((num>>i)&1);if(trie[c][op^1]) //优先走和当前位不同的路c=trie[p][op^1];else c=trie[p][op];}return val[c];
}
int query(int num)
{ //查询所有数中和num异或结果最小的数int c=0;for(int i=31;i>=0;i--){int op=((num>>i)&1);if(trie[c][op]) //优先走和当前位相同的路c=trie[p][op];else c=trie[p][op^1];}return val[c];
}

通过上面的代码,我们可以发现:

1. 01字典树是一棵最多32层的二叉树,其每个节点的两条边分别表示二进制的某一位的值是 0 还是 1,将某个路径上边的值连起来就得到一个二进制串。

2.节点个数为 1 的层(最高层)节点的边对应着二进制串的最高位。

3.以上代码中,trie[i] 表示一个节点,trie[i][0] 和 trie[i][1] 表示节点的两条边指向的节点,val[i] 表示节点的值。

4.每个节点主要有 4个属性:节点值、节点编号、两条边指向的下一节点的编号。

5.节点值 val为 0 时表示到当前节点为止不能形成一个数,否则 val[i]=数值。

6.可通过贪心的策略来寻找与x异或结果最大(最小)的数,即优先找和x的二进制的未处理的最高位值不同(相同)的边对应的点,这样保证结果最大。

【例题】

下面让我们结合题目来理解代码实现

以hdu 4825 Xor Sum 为例:

给出n个数和m次询问,每次询问给出一个数x,问在n个数中哪个数与x异或值最大?

代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
int a[maxn],vis[maxn*3];
int trie[maxn*3][3];
int pos;
void add(int x,int id)
{int c=0,op;for(int i=31;i>=0;i--){op=((x&(1<<i))!=0); //取出x的二进制的第i位(从右往左数if(trie[c][op]==0)trie[c][op]=pos++;c=trie[c][op];}vis[c]=id;
}
int get(int x)
{int c=0,op;for(int i=31;i>=0;i--){op=((x&(1<<i))!=0);if(trie[c][op^1]) //尽可能走与当前位不同的点c=trie[c][op^1];elsec=trie[c][op];}return a[vis[c]];
}
int main()
{int t; scanf("%d",&t);for(int k=1;k<=t;k++){int n,m; scanf("%d%d",&n,&m);pos=1;memset(trie,0,sizeof(trie));memset(vis,0,sizeof(vis));for(int i=1;i<=n;i++){scanf("%d",&a[i]);add(a[i],i);}printf("Case #%d:\n",k);for(int i=1;i<=m;i++){int c; scanf("%d",&c);printf("%d\n",get(c));}}return 0;
}

【拓展】

我们还可以写出带删除的字典树。只要定义一个数组记录当前节点的访问次数,如果次数为1且为要删去的数字的访问节点,那么置0即可。这里不再赘述。

【引用参考】

浅谈Trie树

01字典树 详解

【推荐】

01字典树专题(题目合集)

字典树与01字典树详解相关推荐

  1. 字典树,01字典树,可持续化01字典树(总结+例题)

    目录 字典树 01字典树 字典树例题: power oj 2390: 查单词 HDU 1671 Phone List HDU 1004Let the Balloon Rise HDU 1075 Wha ...

  2. 字典树 与 01字典树

    字典树可以降低空间复杂度:01字典树可以降低时间复杂度. 字典树:又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种.典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经 ...

  3. 线段树扫描线求矩形周长详解

    线段树扫描线求矩形周长详解 原创 wucstdio 最后发布于2018-04-24 16:12:09 阅读数 841 收藏 发布于2018-04-24 16:12:09 版权声明:本文为博主原创文章, ...

  4. K近邻算法和KD树详细介绍及其原理详解

    相关文章 K近邻算法和KD树详细介绍及其原理详解 朴素贝叶斯算法和拉普拉斯平滑详细介绍及其原理详解 决策树算法和CART决策树算法详细介绍及其原理详解 线性回归算法和逻辑斯谛回归算法详细介绍及其原理详 ...

  5. 蒙特卡洛树搜索(MCTS)详解

    蒙特卡洛树搜索(MCTS)详解 蒙特卡洛树搜索是一种经典的树搜索算法,名镇一时的 AlphaGo 的技术背景就是结合蒙特卡洛树搜索和深度策略价值网络,因此击败了当时的围棋世界冠军.它对于求解这种大规模 ...

  6. python for item in items,python 字典item与iteritems的区别详解

    综述迭代器 对于原生支持随机访问的数据结构(如tuple.list),迭代器和经典for循环的索引访问相比并无优势,反而丢失了索引值(可以使用内建函数enumerate()找回这个索引值).但对于无法 ...

  7. 背包问题动态规划matlab,01背包问题动态规划详解

    计算机算法分析考试:动态规划0-1背包问题,怎么算她说她没醉,却一直摇摇晃晃掉眼泪:你说你爱她,却从未想过给她一个家. 要考试了,老师给划重点有一题:动态规划0-1背包问题,怎么算. 怎么理问题描述: ...

  8. AVL树的实现(图文详解)

    AVL树的实现 AVL树定义 AVL树其实就是一棵特殊的二叉树,为什么会出现AVL树,AVL树比普通二叉树优势在什么地方呢? 我们知道,一棵普通的二叉搜索树,以其特殊的性质(左<根<右), ...

  9. Python字典的11个方法超级详解

    Python字典是一种可变容器模型,且可存储任意类型对象,如字符串.数字.元组等其他容器模型. print(dir(dict)) ['clear', 'copy', 'fromkeys', 'get' ...

最新文章

  1. mysql获取多层嵌套json_使用两个mySQL查询来创建嵌套的JSON数组
  2. LeetCode 322. 零钱兑换(动态规划)
  3. Nginx web服务器搭建
  4. Linux on Power 上的调试工具和技术
  5. 老程序员为什么从不使用 Java 自带的序列化?
  6. 通过共享文件夹来进行前后端独立开发
  7. java刷题--69x的平方根
  8. 使用elasticSearch实现以图搜图
  9. 新手如何租用阿里云服务器(图文教程)
  10. 头像设计,如何用PS制作个性头像
  11. Linux系统命令与Shell编程
  12. unity资源包导入错误 Failed to import package with error Couldnt decompress package
  13. Python爬取猫眼「碟中谍」全部评论~
  14. ZTE/中兴Blade N880 root教程_方法
  15. 结构体之选夫婿(冒泡加快排)
  16. My命名空间——VB.NET
  17. PhantomJS NodeJS 在京东网站前端监控平台的最佳实践
  18. 全志A83T camera驱动移植
  19. tdscdma手机linux,中兴通讯推出采用Linux系统的双模双待TD-SCDMA手机
  20. 瞎想-自制液晶屏改触摸屏

热门文章

  1. 【机器学习】求矩阵的-1/2次方的方法
  2. 5款简单实用的WIN10软件推荐
  3. 【C++】命名空间(namespace) 以及理解using namespace std
  4. Ajax中的beforeSend函数使用
  5. Android webview退出后崩溃问题(OPPO和vivo手机有问题)
  6. python相关操作(二)控制手机操作
  7. vue3使用echarts开发上海人口密度地图
  8. 人体血氧饱和度监测方法研究
  9. cas单点登录后重定向次数过多问题以及调试cas-dot-net-client
  10. css表格中怎么设置表格间距,css如何设置表格间距