数据结构:搜索二叉树

news2024/9/25 15:03:48

前言

在前面我们已经学习了二叉树的基础操作,但是,仅仅是二叉树,没有太大的作用啊,存数据效果没有顺序表和链表好,那为啥还要学二叉树呢?
这不就来了嘛,给二叉树增加一些性质,作用不就出来了嘛。
本篇文章将介绍二叉树的进阶版本,给二叉树增加了搜索特性,搜索二叉树

搜索二叉树的概念和性质

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
1. 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
2. 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
3.它的左右子树也分别为二叉搜索树

在这里插入图片描述

注意:要求整棵左子树的值都小于根节点的值,而不仅仅是要求左节点的值小于根节点的值,对于右子树也是如此

ps:当然也可以处理成左子树大,右子树小,不过普遍来说,都是构成左子树小,右子树大的。


搜索二叉树的操作

这里我们先给一个数组,用这个数组的数据构成搜索二叉树
在这里插入图片描述

搜索二叉树的遍历

根据搜索二叉树的性质,我们知道,左子树的值全部都小于根节点的值,右子树的值全部都大于根节点的值。

所以,当我们采用中序遍历就可以得到有=升序的序列。

void INORDER(Node* root)
{
	if (root == nullptr)
		return;

	INORDER(root->_left);
	cout << root->_val << " ";
	INORDER(root->_right);
}

在这里插入图片描述

二叉搜索树的查找

假设要寻找的值是key

牢记搜索二叉树的性质,从根节点开始向下寻找,
当key > 当前节点的时候,去右子树寻找即可
当key < 当前节点的时候,去左子树寻找即可
找到了就返回true,没找到就返回false

bool find(const DataType& key)
{
	Node* cur = _root;
	while (cur)
	{
		if (key > cur->_val)
		{
			cur = cur->_right;//大了就去右子树找
		}
		else if (key < cur->_val)//小了就去左子树找
		{
			cur = cur->_left;
		}
		else
		{
			return true;
		}
	}

	return false;
}

在这里插入图片描述

注意:思考搜索二叉树查找的时间复杂度
是不是很多同学认为是O(logN)?
实际上并不是,应该查找高度次,但是高度就是logN了吗?
事实上,存在极端情况,二叉树会退化成单边二叉树,此时树的高度就是n - 1,
此时,时间复杂度是O(N)。


搜索二叉树的插入

记要插入值key

要插入首先要找到该在何处插入。

显然,这里的思路和查找类似
当key大了,就往右子树走
当key小了,就往左子树走
直到走到空的位置,可以插入。

找到位置后,用key构建一个新的节点,链接到到搜索树上去即可。

那么这里就需要寻找父节点了,所以,我们需要引入一个parent指针来标记父亲节点。

但是?究竟是插在父亲节点的左子树还是右子树呢?
去和父节点的值进行比较即可。
大于父节点的值,插在右边,小于父节点的值插在左边。

对于插入,如果搜索二叉树里面已经存在了该key值,那么就不重复插入,所以搜索二叉树就有了去重的特性

bool insert(const DataType& key)
{
	if (_root == nullptr)
	{
		_root = new Node(key);
		return true;
	}

	Node* cur = _root;
	Node* parent = nullptr;

	while (cur)
	{
		if (key > cur->_val)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (key < cur->_val)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return false;//已经有了,就不重复插入
		}
	}

在这里插入图片描述

搜索二叉树的删除

搜索二叉树最难的部分就是删除,这里需要仔细体会

对于删除节点,我们会发现树里面有三种节点

  1. 当前节点没有子节点
  2. 当前节点只有一个子节点
  3. 当前节点有两个子节点

对于这三种情况我们都需要进行思考。

  1. 当前节点没有子节点
    此时我们只需要释放该节点,将父亲节点指向空即可
  2. 当前节点只有一个子节点
    a. 左子树为空
    将父亲节点指向右子树,释放当前节点即可
    b. 右子树为空
    将父亲节点指向左子树,释放当前节点即可
  3. 当前节点有两个子节点(最复杂的情况

    此时,我们就需要去找到一个合适的值,来替代当前节点。
    很明显,这个值一定要大于左子树,小于右子树
    那么,也就是,我们需要找到左子树的最大值,或者右子树的最小值来替代当前节点
    这里我们以寻找右子树的最小值为例。
    如何寻找右子树的最小值?
    右子树的最小值一定在右子树的最左边,否则就会有更小的值(这里读者理解不了的话,可以画个图理解一下)
    所以这里只需要设置一个指针rightMin,从cur->right开始一路向左,直到左子树为空停止。
    此时rightMin就是右子树的最小值。
    这是将rightMin的值给cur即可,然后删除rightMin节点
    这里删除非常简单,只需要让rightMin的父亲的左指向rightMin的右即可。(所以需要引入rightMinP指针记录rightMin的父亲)

算法实现过程中的一些细节:
(1)首先对于第三种情况,我们去寻找右子树的最小节点,就是寻找右子树的最左边,但是如果右子树没有左节点怎么办?
在这里插入图片描述
比如这里要删除8,右子树没有左节点,此时要进行特判,让cur的右指向rightMin的右。

(2)对于1,2两种情况,需要父亲节点指向子节点的左或者右
但是,对于根节点来说,根节点没有父亲,就会出现空指针问题。
在这里插入图片描述
对于10这个节点就没有父亲节点,
那么此时就需要将root 指向 cur的右
右子树为空类似。

bool erase(const DataType& key)
{
	if (_root == nullptr)
		return false;

	Node* cur = _root;
	Node* parent = nullptr;

	while (cur)
	{
		if (key > cur->_val)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (key < cur->_val)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			//找到了,开始删除
			if (cur->_left == nullptr)
			{
				//左边为空
				if (parent == nullptr)//特判一下
				{
					_root = cur->_right;
					delete cur;
					return true;
				}

				if (cur == parent->_left)
				{
					parent->_left = cur->_right;
				}
				else
				{
					parent->_right = cur->_right;
				}

				delete cur;
				return true;
			}
			else if (cur->_right == nullptr)
			{
				//右边为空
				if (parent == nullptr)
				{
					_root = cur->_left;
					delete cur;
					return true;
				}
				
				if (cur == parent->_left)
				{
					parent->_left = cur->_left;
				}
				else
				{
					parent->_right = cur->_left;
				}
				
				delete cur;
				return true;
			}
			else
			{
				//两个节点都为空
				Node* rightMin = cur->_right;
				Node* rightMinP = cur;

				//找右子树最小的节点
				while (rightMin->_left)
				{
					rightMinP = rightMin;
					rightMin = rightMin->_left;
				}

				cur->_val = rightMin->_val;

				if (rightMin == rightMinP->_right)
				{
					cur->_right = rightMin->_right;
				}
				else
				{
					rightMinP->_left = rightMin->_right;
				}

				delete rightMin;//这里不删cur
				return true;
			}
		}
	}

	return false;//找不到返回false;
 }

在这里插入图片描述

搜索二叉树的应用

搜索二叉树分为k型和kv型,上述就是以k型为例,当然,kv型类似,聪明的读者肯定能举一反三。

1、k型
K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。

比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。

上面的例子就是k型,就不举例子了。

2、kv型
KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生活中非常常见:
比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;
再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。

void TestBSTree3()
{
 // 输入单词,查找单词对应的中文翻译
 BSTree<string, string> dict;
 dict.Insert("string", "字符串");
 dict.Insert("tree", "树");
 dict.Insert("left", "左边、剩余");
 dict.Insert("right", "右边");
 dict.Insert("sort", "排序");
 // 插入词库中所有单词
 string str;
 while (cin>>str)
 {
 BSTreeNode<string, string>* ret = dict.Find(str);
 if (ret == nullptr)
 {
 cout << "单词拼写错误,词库中没有这个单词:" <<str <<endl;
 }
 else
 {
 cout << str << "中文翻译:" << ret->_value << endl;
 }
 }
}

在这里插入图片描述


剩下的构造 ,析构,拷贝构造比较简单,这里就不讲解了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2163892.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

徐州网站建设的最新趋势与技术

随着徐州经济的快速发展&#xff0c;网站建设在当地越来越受到重视。不同类型的企业和组织都希望通过优化他们的在线形象来吸引更多客户。因此&#xff0c;了解目前的趋势与技术是实现高效网站建设的关键。 1. 响应式设计 响应式设计是当前网站建设的核心趋势之一。无论是桌面…

毕业设计选题:基于ssm+vue+uniapp的校园失物招领小程序

开发语言&#xff1a;Java框架&#xff1a;ssmuniappJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;M…

基于VITA57.1标准的4通道2.8GSPS 16位JESD204B接口DAC播放子卡

板卡概述 FMC171是一款基于VITA57.1标准的4通道2.8GSPS采样率16位分辨率JESD204B接口DAC回放子卡模块。该板卡采用ADI公司的AD9144&#xff0c;实现4路模拟输出&#xff0c;数字接口通过8通道的JESD204B进行互联。 该板卡支持本地参考时钟、外部输入参考时钟、外部VCO时钟以及…

门面模式详解:提高代码可维护性的利器

门面模式是一种结构型设计模式&#xff0c;它通过为复杂的子系统提供一个统一的接口&#xff0c;使得子系统更容易使用。门面模式隐藏了系统的复杂性&#xff0c;并向客户端提供了一个简化的接口。 一&#xff0c;门面模式的结构 门面模式&#xff08;Facade Pattern&#xf…

有没有遇到过Mybatisplus插件失效?

背景 相信mybatisplus应该很多人都用过,当然有些人在项目中使用的是tk.mybatis,那么今天说到这个话题原理都是一样,首先mybatisplus会有很多插件,这些插件都会形成一个拦截器链路,具体可以学习下原理,这个地方就不过多叙说了,毕竟不是本篇博文的重点,那言归正传,比如…

普渡大学和麻省理工学院合作开发集成视触觉指尖传感器的5自由度抓手

虽然机器人已经开始在现代制造业、医疗、服务业等领域进行渗透&#xff0c;但对于机器人尤其是机械臂的操作能力&#xff0c;仍然有很大的提升空间&#xff0c;传统多指机器人手虽然能够实现复杂的操作任务&#xff0c;但其高度冗余性也带来了不必要的复杂性。近日来自普渡大学…

WebRTC中的维纳滤波器实现详解:基于决策导向的SNR估计

目录 1. 维纳滤波器的基本原理2. WebRTC中的维纳滤波器实现3. 代码逐步剖析4. 总结 在WebRTC的噪声抑制模块中&#xff0c;维纳滤波器&#xff08;Wiener Filter&#xff09;是一种非常常见且重要的滤波器&#xff0c;用于提高语音信号的清晰度并抑制背景噪声。本文将详细解释维…

Hugging Face Transformer:从原理到实战的全面指南

一、前言 我们知道ChatGPT和所有预训练大语言模型的核心是什么&#xff1f;其实就是 Transformer&#xff0c;Hugging Face 的火爆离不开他们开源的这个 Transformers 库。这个开源库里有数万个我们可以直接调用的模型。很多场景下&#xff0c;这个开源模型已经足够我们使用了。…

【Pyside】pycharm2024配置conda虚拟环境

知识拓展 Pycharm 是一个由 JetBrains 开发的集成开发环境&#xff08;IDE&#xff09;&#xff0c;它主要用于 Python 编程语言的开发。Pycharm 提供了代码编辑、调试、版本控制、测试等多种功能&#xff0c;以提高 Python 开发者的效率。 Pycharm 与 Python 的关系 Pycharm 是…

2024年9月24日---关于MyBatis框架(3)

五 Mybatis的缓存 5.1 Mybatis缓存简介 缓存(cache)&#xff1a;提前把数据存放到缓存当中&#xff0c;下一次再使用的时候&#xff0c;直接从缓存中拿&#xff0c;而不用再次去数据库中查询一次了。这样的优势在于&#xff1a;通过减少IO的⽅式&#xff0c;来提⾼程序的执⾏…

28岁打算转行靠谱么,这个年龄转行,有什么适合的行业么?

前言 大家好&#xff0c;我是职场小油条播妞&#xff0c;最近被一组表情包洗脑了。 &#xff08;强哥表情包我就不放了&#xff0c;大家自行脑补&#xff09; 我要让老板知道&#xff0c;招我进来&#xff0c;就要承担一定的风险 既然领导把岗位交给咱&#xff0c;证明这岗位…

short-link笔记

1.Accessors(chain true) (见于Result类的注解) 不写默认为false&#xff0c;当该值为 true 时&#xff0c;对应字段的 setter 方法调用后&#xff0c;会返回当前对象。 -->可用于链式编程 参:Accessors 注解详解-CSDN博客 2.关键信息脱敏 利用将class通过jackon序列化为…

Azure Data Box 80 TB 现已在中国区正式发布

我们非常高兴地宣布&#xff0c;Azure Data Box 80 TB SKU现已在 Azure 中国区正式发布。Azure Data Box 是 Azure 的离线数据传输解决方案&#xff0c;允许您以快速、经济且可靠的方式将 PB 级数据从 Azure 存储中导入或导出。通过硬件传输设备可加速数据的安全传输&#xff0…

Mybtais高级结果映射-多对一表映射

前言 从前我们只进行单表的sql操作&#xff0c;但是如果涉及多张表的操作&#xff0c;原先的映射关系就不太适用了&#xff0c;因此这里将会介绍Mybatis的高级结果映射技巧 准备工作 准备两张数据库表&#xff0c;一个员工表&#xff0c;一个部门表&#xff08;我们使用oracle的…

彻底理解前端模块化

目录 引入历史问题 CommonJSexports导出module.exports导出require导入加载过程缺点 AMD规范&#xff08;基本不用&#xff09;require.js使⽤ CMD规范&#xff08;基本不用&#xff09;SeaJS的使⽤ ES Module简单使用export关键字import关键字export和import结合default⽤法im…

自然语言处理在人工智能领域的发展历程,以及NLP重点模型介绍

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下自然语言处理在人工智能领域的发展历程&#xff0c;以及NLP重点模型介绍。本文详细介绍了自然语言处理的发展历程&#xff0c;同时深入探讨了各种自然语言处理模型的原理与应用。文章首先回顾了自然语言处理技术的发…

读论文《OmniGen: Unified Image Generation》

OmniGen演示了在单一框架内执行各种图像生成任务的能力。此外&#xff0c;它还具有推理能力和语境学习能力。 论文地址&#xff1a;2409.11340v1 (arxiv.org) 项目地址&#xff1a;GitHub - VectorSpaceLab/OmniGen 项目目前还不完整&#xff0c;论文展现的通用性十分强大&am…

【Java】Java中接口与内部类详解

目录 引言 一、接口&#xff08;Interface&#xff09; 1.1 接口的定义 1.1.1 接口的特点 1.2 接口的实现 1.3 接口的继承 1.4 接口的注意事项 1.5 代码示例 二、内部类&#xff08;Inner Class&#xff09; 2.1 内部类特点 2.2 成员内部类 2.2.1 对象的创建 2.…

基于SpringBoot社区疫情信息管理系统的设计和实现

文未可获取一份本项目的java源码和数据库参考。 选题的意义 保护好人民群众的基本安全&#xff0c;贯彻党的领导下中国一盘棋的基本准则。将病毒隔绝在外&#xff0c;信息系统的存在显得至关重要&#xff0c;应对新型冠状病毒肺炎疫情治理的实践背景。实时关注更新疫情动态&a…

开放式耳机哪个牌子的质量好?2024开放式蓝牙耳机排行榜前5强推荐!

​开放式耳机因其舒适的佩戴感和较小的耳朵损伤而广受好评。曾经让人头疼的漏音问题&#xff0c;随着科技的进步&#xff0c;已经有了显著改善&#xff0c;不再是个大问题。在拥挤的环境中&#xff0c;耳机音量过大不仅会打扰他人&#xff0c;也会让自己感到不适。因此&#xf…