C++学习第八篇——字典树

2023-11-14

学习了之前的树状结构,接下来就可以利用树状结构存储数据了。
首先什么是字典树?
字典树就是利用树的结构按照字典的原理进行存储的数据结构,树的结构我们了解了,字典是什么样的呢,我们通常去查英文单词的时候,往往都是英文字母a,b,c,d…x,y,z这样一个顺序,利用这样的原理我们知道有字典序这样的顺序。字典树也是如此,例如有两个单词application,apple,我们查字典时通常是按a->p->p->l->接着下去,然后先发现了apple,再发现了application,我们会发现这些有着相同前缀的单词查询时进行的方式是相同的,为了减少内存的使用,而且便于查找,它的优点很明显,有相同前缀的单词前缀部分只用存储一次。

这里给出字典树最常用的模板,当然不同的题目需要不同的删改变动灵活运用,需要注意的一点就是,我们知道一个树结构的分枝越多和深度越深,那么递归调用起来很可能爆栈,所以要适时进行改动,另外有些内存优化过的编译器更容易出现这样的情况。

和之前一样利用注释和代码结合,先给大家清晰直观的图示,了解一下。
在这里插入图片描述
insert函数过程:首先apple单词插入,char *s指向a字符,for循环遍历,遍历至root里next数组a结点时,也就是下标为0的数组值。它还是NULL,那么新建,将值赋进去,同理延伸至apple完成,e点时该单词结束标记此时的e结点为单词,isword=true。当application单词插入时,之前的appl都是已经建立好的了,只用在原来的基础上将prefix++即可,那么新的分枝出现了,也就是appli,与apple同理继续延伸下去。

struct node//树结构
{
	char *s;//字符指针存储当前结点字符
	int prefix;//出现多少以当前字符串为前缀的字符串,后面会进行深入解释
	bool isword;//截止当前,该字符串是否为单词
	node *next[26];//26个字母,从a的下标为0开始
	node()//初始化函数,相当于构造函数初始化数据
	{
		s = NULL;//当前结点为空
		prefix = 0;//0个单词以此段为前缀
		isword = false;//不是单词
		memset(next,0,sizeof(next));
	}
}*root;
void insert(node *root,char *s)//插入函数
{
	node *p = root;//将p赋值为我当前插入的结点
	for(int i=0;s[i];i++)//遍历单词
	{
		int x = s[i] - 'a';//将单词顺序转化为下标存储
		p->s=s+i;//p结点存储的字符指向char * s的第i位字,char * 结构需要通过调用指针+-来获取字符指针
		if(p->next[x] == NULL)//很明显,如果没有指向下一个的就新建一个
			p->next[x] = new node;
		p = p->next[x];//接着p就会指向它,也就是指针向下一个字母前进
		p->prefix++; //出现次数+1
	}
	p->isword=true;//遍历的最后,末尾p即为单词的结尾,所以它是一个完整的单词
}
bool del(node *root,char *s)//删除函数,这个函数通常很少使用,删除某个单词原理与插入相同
{
	node *p = root;
	for(int i=0;s[i];i++)
	{
		int x = s[i]-'a';
		if(p->next[x]==NULL)
			return false;
		p = p->next[x];
	}
	if(p->isword)
		p->isword=false;
	else return false;
	return true;
}
bool search(node *root,char *s)//查找单词
{
	node *p=root;
	for(int i=0;s[i];i++)
	{
		int x=s[i]-'a';
		if(p->next[x]==NULL)//和insert中相同,只需要向下找到所需分枝即可,如果不存在,则说明不含有该单词返回false即可
			return false;
		p = p->next[x];
	}
	return p->isword;//如果到了末尾,返回当前分枝是否为单词,例如appl就不是一个单词,但是可以访问到达,返回appl的l结点的isword即可
}
int count(node *root,char *s)//记录以当前字符串为前缀的单词个数
{
	node *p= root;
	for(int i=0;s[i];i++)
	{
		int x=s[i]-'a';
		if(p->next[x]==NULL)
			return 0;
		p = p->next[x];
	}//遍历找到当前字符串的最后一个分枝返回出现次数即可,也就是prefix的值
	return p->prefix;
}
char word[11];//单词长度,利用char 数组存储
char pre[11];
int main() 
{
	root = new node;
	while(gets(word))
	{
		if(strcmp(word,"")==0) break;
		insert(root,word);
//		cout<<word<<endl;
	}
//	cout<<"next"<<endl;
	while(gets(pre))
	{
		if(strcmp(pre,"")==0) break;
		cout<<count(root,pre)<<endl;
	}
}

很多人可能对char * s和char s[]之间产生疑问,c++中字符串的存储方式实际上是利用char数组进行的,指针实际上就相当于一个数组,只是他没有固定的大小,就好比如果我需要存1000000的字符串和10的字符串,如果char数组那我只能开一个1000000大小的,相对于10大小的,会造成浪费,所以char *没有固定大小是在内存上提供了遍历,本质上与char数组对字符串的处理是相同的。

main函数里的操作时,先进行插入单词,然后输入一些测试查询以此为前缀的单词数量
通常对于字典树的操作,在建树完成后,我们需要知道以下几个问题:

  • 该字符串是否为单词
  • 以此字符串为前缀的单词数量
  • 有多少单词经过该分枝节点
  • 最长的公共前缀,prefix为n的所有节点最深的那个,它的深度即为所求

想要求出这些问题的解,只需要对上述模板进行变形即可,熟练使用后,便很容易操作起来。
字典树学会了的话,不妨看看字典树的延伸——AC自动机,难度过大的话可以先收藏起来以后再学哦!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++学习第八篇——字典树 的相关文章

随机推荐

  • 数据结构与算法实验-实验一:线性表基本操作

    线性表基本操作 文章目录 线性表基本操作 题目1 题目2 题目3 题目1 线性表是最常见和常用的ADT 假设线性表的元素为整数 请基于顺序存储结构实现线性表ADT 基本功能包括 1 建立线性表 输入有两行 第一行是一个整数n 线性表的长度
  • SpringMVC自定义视图完成步骤 和 视图解析的源码剖析

    自定义视图完成步骤 7 2 1自定义视图完成步骤 1 自定义视图 创建一个 View 的 bean 该 bean 需要继承自 AbstractView 并实现 renderMergedOutputModel 方法 2 并把自定义 View
  • 【项目实战】在win10上安装配置Hadoop的环境变量

    一 说明 注意 该教程适用于 远程连接Linux上的Hadoop集群 因此本步骤是不需要在本地再下载hadoop的 在win10操作系统上 运行Hadoop以及其相关依赖包 比如Hbase依赖包 时 我遇到的情况是 我需要使用SpringB
  • 素数(埃式筛法、线性筛法)

    文章目录 素数判断方法 埃式筛法 线性筛法 区间筛法 质因数分解 例题 第一题 第二题 第三题 素数判断方法 最简单的就是从 2 n 1 都去与 n 取余 看是否能整除 bool prime int n for int i 2 i lt n
  • 使用linuxdeployqt在linux下进行Qt打包发布(超详细)

    首先 来说下 本教程实现的功能 在linux下对开发的Qt应用 进行拷贝依赖文件so等 并打成deb安装包 实现可安装 卸载 安装完毕自动在开始菜单下和桌面添加快捷方式 卸载后自动删除快捷方式 以及删除应用生成的log文件 测试环境 ubu
  • 自学Android资料大全

    学习级别 很多人都往往划分成入门 初级 中间 骨灰级等 这里就简单地划分为两级 基础篇和进阶篇 另外 本文涉及到的所有书籍都是在学习过程中所读过的比较经典的一些书籍 一 基础篇 看书的姿态 学习过程往往大家都需要看书 网上一搜 往往会有一大
  • 浪潮服务器更换硬盘_携手希捷,浪潮领先业界完成希捷银河(Exos)X18企业级硬盘评测...

    全球领先的数据存储解决方案提供商希捷科技 NASDAQ STX 宣布 携手浪潮完成对希捷银河 Exos X18企业级硬盘的评估 该硬盘拥有目前业界最高的18TB超大容量 性能卓越 用于承载大规模数据中心的海量数据 双方在实际工作负载环境中测
  • Python @函数装饰器及用法(超级详细)

    使用 符号引用已有的函数 比如 staticmethod classmethod 后 可用于修饰其他函数 装饰被修饰的函数 那么我们是否可以开发自定义的函数装饰器呢 答案是肯定的 当程序使用 函数 比如函数 A 装饰另一个函数 比如函数 B
  • AcWing 420. 火星人

    y总讲得很好 学到很多所以安利一下 转载自Acwing yxc 算法 贪心 全排列 O nm O nm 这道题目可以直接用next permutation函数来做 这里我们考虑一下next permutation函数的原理 然后手动实现一遍
  • c++ modbusTCP

    Modbus TCP是一种基于TCP IP协议的Modbus协议 它允许Modbus协议通过以太网进行通信 在C 中 可以使用第三方库来实现Modbus TCP通信 例如libmodbus和QModbus 使用libmodbus库实现Mod
  • 介绍一种巧妙的删除程序自己的方法

    介绍一种巧妙的删除程序自己的方法 vcbear 近日看到网友询问如何实现程序运行之后把自己删除的方法 不知大家对木马甚么的兴趣实在太浓 还是想要这样的效果 用户只要一运行程序 可执行文件就没有了 可是程序还是在跑 胆小的只怕要喊 鬼呀 老婆
  • Pytorch英文官方文档学习笔记(三、Torch.nn及torch.optim)

    一 nn Module的使用 Every module in PyTorch subclasses the nn Module 自己定义的每个module都一定是nn Module的子类 pytorch在nn Module中 实现了 cal
  • lod地形

    lod地形 2014 05 17 23 29 1471人阅读 评论 0 收藏 举报 分类 图形学 17 OGRE相关 75 目录 http blog sina com cn s blog 5e3213f30100zxet html 最近在看
  • 对于同步和非同步,阻塞和非阻塞,BIO,NIO的概念的回顾

    同步和异步 同步和异步其实是指CPU时间片的利用 主要看请求发起方对消息结果的获取是主动发起的 还是被动通知的 如下图所示 如果是请求方主动发起的 一直在等待应答结果 同步阻塞 或者可以先去处理其他事情 但要不断轮询查看发起的请求是否有应答
  • 阿里云服务器实现 frp 内网穿透

    更多精彩内容请访问我的新博客站点 前言 前几天在一台具有公网IP的 vultr 云服务器上实现了 frp 内网穿透 参考链接 可以从寝室 ssh 登录到教研室的服务器 但是由于 vultr 的云服务器位于国外的节点 连接速度太慢了 导致连接
  • 信息采编功能扩展开发心得

    AEAI Portal门户为前端页面集成层而设计 在使用上简单 便捷 即使是非技术人员 通过操作文档也能够很好地将网站配置出来 不需要自身有很强的代码能力 同时门户平台搭配数通畅联的其他产品和组合方案 能够帮助企业快速搭建集成的 内容丰富
  • GoLand之学习之路--持续更新

    GoLand之学习之路 持续更新 基础包 time 获取当前时间 Bytes 多个 byte数组合并成一个 byte 高级用法 interface 得到调用者函数名 pprof sync Once 命令行参数实例 使用小技巧 string和
  • H264解码深度解析——DM8168 OMX从H264文件读取一帧数据(do chunking of h264)

    源码来源 TI DM8168 EZSDK OMX examples decode display 基本执行流程如下 Decode GetNextFrameSize H264 ParsingCtx pc 函数源码 加注释 如下 Decode
  • Linux vim使用方式学习纪要

    vim学习 在Linux下工作和学习 离不开vi和vim的使用 巧妙记住各种模式下的各种指令 不仅可以在工作中大大提高效率 还能装一个满分的哔 我比较菜 只会下面最常用的4种模式中的部分组合技能 不过好像在我的开发工作中已经完全足够了 模式
  • C++学习第八篇——字典树

    学习了之前的树状结构 接下来就可以利用树状结构存储数据了 首先什么是字典树 字典树就是利用树的结构按照字典的原理进行存储的数据结构 树的结构我们了解了 字典是什么样的呢 我们通常去查英文单词的时候 往往都是英文字母a b c d x y z