c++实现二叉搜索树(中)

 小吉我今天更新了,惊不惊喜,意不意外,更新频率非常好(棒棒的)。小吉计划把二叉搜索树的知识更新完(预计在这几天更完),然后会有一段时间停更,因为小吉我要准备期末考试(几周时间速成🤪),玩归玩,闹归闹,绝对不能挂科!也祝大家期末考试门门过💪
 言归正传,接下来就要进入我们今天的学习了,上一篇blog讲到put(存储键值和对应的实值)这个方法,这篇blog主要讲二叉搜索树的前任后任和删除操作,浅浅预告一下,这篇blog讲解的内容会有一些难🫣

实现大纲:
predecessor——查找键值的前任(即前驱节点)
successor——查找键值的后任(即后继节点)
deletenode——根据键值删除

predecessor——查找键值的前任(即前驱节点)并返回前驱节点的实值

二叉搜索树2
实现之前先解释一下什么是前任(不是我们传统意义上理解的前任啦🫣),假设传参的键值是4,它的前任就是3,就是键值的前一个数
有可能有小伙伴第一反应是用中序遍历(左值右)求解,以上图为例,中序遍历的结果就是1234567,那前任就很好找了,但实际上我们并不用中序遍历的方式实现,因为性能不高(what a pity)

前任思路:1)节点有左孩子,此时的前驱节点就是左孩子中的最大值
2)节点没有左孩子,若离它最近的祖先自从左而来,此祖先即为前驱节点

 在上面思路中,情况一比较好理解,相信很多小可爱看情况二就会比较难理解,没事,小吉我最开始也是一脸懵,接下来小吉画一张图来帮助大家解释一下
解释
 现在小伙伴们大致的思路应该明白了,可到了真正实现时,又有疑惑,我们应该怎么找到离它最近的从左而来的祖先呢?

我们传参的是一个键值,在找前驱节点之前,我们应该先遍历二叉搜索树,查找传入的键值是否存在,在这个过程中我们就可以创建一个指针,当往右时,把节点记录下来,最后指针的指向就是我们要找的离它最近且自左而来的节点

代码实现👇:

string BSTTree::predecessor(int key)
{
	BSTnode* p = _root;
	BSTnode* ancestorFromleft = nullptr;
	while (p != nullptr)//先查找键值是否存在
	{
		if (p->_key > key)
		{
			p = p->_left;
		}
		else if (p->_key < key)
		{
			ancestorFromleft = p;
			p = p->_right;
		}
		else
		{
			break;//找到退出循环
		}
	}
	if (p == nullptr)
	{
		return "没找到节点";
	}
	//存在键值后找键值的前任
//找到节点 情况1:节点有左子树,此时前任就是左子树的最大值
	if (p->_left != nullptr)
	{
		return max(p->_left);
	}
//找到节点 情况2:节点没有左子树,若离它最近的、自左而来的祖先就是前任
	return (ancestorFromleft != nullptr) ? ancestorFromleft->_value : "没有找到前任";
}

注:用到的max函数是我们上一篇blog中实现的方法,如果有小伙伴对此有问题,可以先去看小吉的上一篇blog,blog传送门🚪:c++实现二叉搜索树(上)

successor——查找键值的后任(即后继节点)并返回后继节点的实值

找后任和找前任非常相似,可以说是换汤不换药,所以小吉在这里只讲思路,不做过多的解释

思路:1)节点有右子树,此时后继节点即为右子树的最小值 2)节点没有右子树,离它最近的祖先自从右而来,此时祖先即为后继节点

代码实现:

string BSTTree::successor(int key)
{
	BSTnode* p = _root;
	BSTnode* ancestorFromright = nullptr;
	while (p != nullptr)
	{
		if (p->_key > key)
		{
			ancestorFromright = p;
			p = p->_left;
		}
		else if (p->_key < key)
		{
			p = p->_right;
		}
		else
		{//找到就退出循环
			break;
		}
	}
	if (p == nullptr)
	{
		return "没有找到节点";
	}
	//找到节点 情况1:节点有右子树,此时后任就是右子树的最小值
	if (p->_right != nullptr)
	{
		return doMin(p->_right);
	}
	//找到节点 情况2:节点没有右子树,离它最近,自右而来的祖先就是后任
	return (ancestorFromright != nullptr) ? ancestorFromright->_value : "没有找到后任";
}

deletenode——根据键值删除

❗❗❗在讲实现之前,小吉先来强调点东西,二叉搜索树中树节点由new开辟,在删除节点时要用delete手动释放,要不然会导致内存泄漏,造成巨大的隐患(不仅在提醒大家,也在提醒小吉自己,因为小吉我经常忘记,这是一个非常不好的编码习惯!)

思路(分为四种情况):
1.删除节点没有左孩子,将右孩子托孤给要删除节点的父节点(parent)
2.删除节点没有右孩子,将左孩子托孤给要删除节点的父节点(parent)
3.删除节点的左右孩子都没有(其实已经被涵盖在情况1和情况2中),把nullptr托孤给要删除节点的父节点(parent)
4.删除节点左右孩子都有,可以将它的后继节点(简称s)托孤给要删除节点的父节点(parent),称s的父节点为sp
(在情况四的基础上又分为两种情况)
1)sp就是被删除节点,此时D(要删除的节点)与s紧邻,只需将s托孤给parent
2)sp不是被删除节点,此时D与s不紧邻,此时需要将s的后代托孤给sp,再将s托孤给parent

哈哈,相信很多小可爱看完上面的思路,跟没思路没什么差别,别急,接下来小吉结合图好好讲解一下
情况一
情况二情况三
 前三种情况已经分析完了,我们可以先写代码实现前三种情况,第四种情况比较复杂,我们等下细讲
前三种都涉及到托孤,我们先实现托孤这个方法

void BSTTree::shift(BSTnode* parent, BSTnode* deleted, BSTnode* child)
{//parent被删除节点的父亲 deleted被删除节点 child被顶上去的节点
	if (parent == nullptr)
	{
		_root = child;
	}
	else if (deleted == parent->_left)
	{
		parent->_left = child;
	}
	else
	{
		parent->_right = child;
	}
}
string BSTTree::deletenode(int key)
{
	BSTnode* p = _root;
	BSTnode* parent = nullptr;
	while (p != nullptr)
	{
		if (p->_key > key)
		{
			parent = p;
			p = p->_left;
		}
		else if (p->_key < key)
		{
			parent = p;
			p = p->_right;
		}
		else
		{
			break;//找到就退出循环
		}
	}
	if (p == nullptr)
	{
		return "没有找到要删除的节点";
	}
	//删除操作
	if (p->_left == nullptr )
	{//情况1:删除的节点没有左孩子,将右孩子托孤给parent
		shift(parent, p, p->_right);
	}
	else if (p->_right == nullptr)
	{//情况2:删除的节点没有右孩子,将左孩子托孤给parent
		shift(parent, p, p->_left);
	}
	else
	{
	  //情况四
	}
	string str = p->_value;
	delete p;
	p = nullptr;//置空,防止悬挂指针
	return str;
}

注:情况三走情况一或情况二的代码都能实现
🤪🤪🤪情况四讲解

情况四代码实现总结:
1)被删除节点找后继
2)处理后继节点的后事
3)后继节点取代被删除节点

情况四(1)
情况四(2)

代码实现:

else
	{//情况4:1.被删除节点找后继 2.处理后继的后事 3.后继取代被删除节点
		BSTnode* s = p->_right;
		BSTnode* sparent = p;
		while (s->_left != nullptr)
		{
			sparent = s;
			s = s->_left;
		}
		if (sparent != p)
		{
			shift(sparent, s, s->_right);//s(后继)不可能有左孩子
			s->_right = p->_right;
		}
		shift(parent, p, s);
		s->_left = p->_left;
	}

👆以上四种情况已经实现完了,二叉搜索树删除操作已实现,上面是使用非递归的方式实现,下面小吉将呈现用递归的方式实现删除操作,大致思路和非递归相同,小吉就不做过多的解释了,直接上代码

//返回值为删除节点后剩下的孩子节点 参数node是删除的起点
BSTnode* BSTTree::doDelete(BSTnode* node, int key,vector<string>& v)//!!!传引用
{
	if (node == nullptr)
	{//如果没找到节点,返回nullptr
		return nullptr;
	}
	if (node->_key < key)
	{
		node->_right=doDelete(node->_right, key,v);
		return node;
	}
	if (node->_key > key)
	{
		node->_left=doDelete(node->_left, key,v);
		return node;
	}
	//找到节点
	v.push_back(node->_value);
//1.只有右孩子
	if (node->_left == nullptr)
	{
		BSTnode* temp = node;
		node = node->_right;
		delete temp;
		temp = nullptr;
		return node;
	}
//2.只有左孩子
	if (node->_right == nullptr)
	{
		BSTnode* temp = node;
		node = node->_left;
		delete temp;
		temp = nullptr;
		return node;
	}
//3.有两个孩子(后继节点)
	BSTnode* s = node->_right;
	while (s->_left != nullptr)
	{
		s = s->_left;
	}
	vector<string> v1;
	s->_right=doDelete(node->_right, s->_key,v1);
	s->_left = node->_left;
	BSTnode* temp = node;
	delete temp;
	temp = nullptr;
	return s;
}

string BSTTree::deletenode2(int key)
{
	vector<string> v;
	_root = doDelete(_root, key, v);
	string str = v[0];
	if (v.empty())
	{
		return "没有找到节点";
	}
	return str;
}

❗这里强调一下实现递归的doDelete函数,只要牢记返回的是删除节点后剩下的孩子节点和传入的是删除的起点,这样递归函数就很好理解了
这篇blog有点难理解,尤其是根据键值删除节点的部分,大家没看懂不要急,多看几遍,多画画图,多写写代码就好了,不要否定自己,你其实很棒的,慢慢来,多给自己一点时间,多点耐心❤️
 学习的时间总是短暂的,这篇blog到这里就已经全部结束了(完结撒花🎉),小吉写这篇blog差不多花了三个小时(着实有点小累),希望大家多多点赞收藏关注(爱你们哦😘❤️😘)

如有错,还望各位大佬多多指点🌹🌹🌹

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

5-1RT-Thread互斥量

5-1RT-Thread互斥量 互斥量斥量的管理方式 互斥量 互斥量又称为互斥型信号量&#xff0c;是一种特殊的二值信号量。以超市的储物柜为例&#xff0c;当用户A存入物品并关闭柜门&#xff0c;则用户A就获得了此格柜子的使用权。此时其他用户无法使用此个柜子&#xff0c;只有当用户…

Idea jdk配置的地方 启动时指定切换的地方

jdk 配置的地方 项目sdk 所在位置 管理添加或删除的地方&#xff0c;增加后&#xff0c;可以在在上面切换 启动时指定版本

正点原子imx6ull 进度条颜色、logo位置上偏或色偏等问题

正点原子imx6ull 进度条改颜色 logo位置上偏或显示色偏等问题 开机进度条logo问题进度条界面全屏logo位置上偏进度条界面logo其他问题进度条界面去掉中间这条杠 uboot界面logo问题不显示uboot界面的打印信息uboot显示logo不理想uboot不显示logo 开机进度条logo问题 进度条界面…

媲美Sora,免费使用!带物理模拟的,文生视频模型

6月13日&#xff0c;知名3D建模平台Luma AI发布最新文生视频模型Dream Machine&#xff0c;向所有用户免费开放使用。 Dream Machine除了支持文本之外&#xff0c;还可使用图片作为引导来生成视频&#xff0c;其生成的视频质量、动作一致性、色彩、光影、饱和度、运镜等方面&a…

EE trade:港股开户指南及所需条件

开通港股账户是许多投资者希望参与香港股票市场的重要步骤。以下是详细的港股开户要求和条件&#xff0c;以及开户流程和注意事项。 一、港股开户的基本条件 1. 证券账户及资金要求 A股证券账户&#xff1a;个人客户申请开通港股账户&#xff0c;需要已经开通上海或深圳的A股…

【YOLOv5/v7改进系列】改进池化层为RT-DETR的AIFI

一、导言 Real-Time DEtection TRansformer&#xff08;RT-DETR&#xff09;&#xff0c;是一种实时端到端目标检测器&#xff0c;克服了Non-Maximum Suppression&#xff08;NMS&#xff09;对速度和准确性的影响。通过设计高效的混合编码器和不确定性最小化查询选择&#xf…

优思学院|如何选择六西格玛黑带的项目?

不管六西格玛的实施着重于变革式的还是渐进式的目标&#xff0c;项目都是六西格玛最核心的部分。选择和使用组织中最好的人才本身并不一定能保证达到最好的结果&#xff0c;项目的选取是领导层无可推卸的责任。选择一个项目意味着什么&#xff1f;领导团队必须将无数的问题、困…

【启明智显分享】Model系列工业级HMI芯片:开源RISC-V+RTOS实时系统,开放!高效!

前言 「Model系列」芯片是启明智显针对工业、行业以及车载产品市场推出的系列HMI芯片&#xff0c;主要应用于工业自动化、智能终端HMI、车载仪表盘、两轮车彩屏仪表、串口屏、智能中控、智能家居、充电桩显示屏、储能显示屏、工业触摸屏等领域。此系列具有高性能、低成本的特点…

Linux 基本指令3

date指令 date[选项][格式] %Y--年 %m--月 %d--日 %H--小时 %M--分 %S--秒 中间可用其他符号分割&#xff0c;不能使用空格。 -s 设置时间&#xff0c;会返回设置时间的信息并不是改变当前时间 设置全部时间年可用-或者&#xff1a;分割日期和时间用空格分隔&#xff…

【Android】实现Recyclerview的Item可以左右侧滑动的效果

项目需要 使用Recyclerview进行列表的数据加载的时候&#xff0c;需要对这个Item进行左右滑动进行操作的功能&#xff0c; 比如这样 需求实现 上面图来源于 https://github.com/anzaizai/EasySwipeMenuLayout 这是一个可以用来进行列表左滑、右滑的项目&#xff0c;可以集…

Linux开机自启/etc/init.d和/etc/rc.d/rc.local

文章目录 /etc/init.d和/etc/rc.d/rc.local的区别/etc/init.dsystemd介绍 /etc/init.d和/etc/rc.d/rc.local的区别 目的不同&#xff1a; /etc/rc.d/rc.local&#xff1a;用于在系统启动后执行用户自定义命令&#xff0c;适合简单的启动任务。 /etc/init.d&#xff1a;用于管理…

借助ChatGPT撰写学术论文,如何设定有效的角色提示词指

大家好&#xff0c;感谢关注。这个给大家提供关于论文写作方面专业的讲解&#xff0c;以及借助ChatGPT等AI工具如何有效辅助的攻略技巧。有兴趣的朋友可以添加我&#xff08;yida985&#xff09;交流学术写作或ChatGPT等AI领域相关问题&#xff0c;多多交流&#xff0c;相互成就…

msvcp140.dll安装步骤,教你解决msvcp140.dll丢失的多种靠谱解决方法

一、msvcp140.dll文件丢失或损坏的影响 1 程序启动问题 当msvcp140.dll文件丢失或损坏时&#xff0c;最直接的后果是依赖于此DLL文件的程序无法正常启动。例如&#xff0c;Adobe系列软件、Microsoft Office套件、Steam游戏平台等&#xff0c;这些软件在启动时如果检测到msvcp…

记录open62541简单有效的编译生成.c和.h文件【OPCUA开源库】

一、下载和安装CMake 虽然说可以通过下面命令安装CMake,但是安装CMake时,通常会安装来自你的操作系统的软件仓库中的版本,这个版本可能不是最新的 sudo apt-get install cmake 如果安装后发现CMake版本低于CMake 3.13是没有办法进行编译的 接下来通过编译源码来升级高版本…

【荷包支付-注册安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞 …

14.shell awk数组

awk数组 awk数组awk数组示例Nginx日志分析 awk数组 1.什么是awk数组 数组其实也算是变量,传统的变量只能存储一个值,但数组可以存储多个值 2.awk数组应用场景 通常用来统计、比如:统计网站访问TOP10、网站url访问TOP10等等 3.awk数组统计技巧 1.在awk中,使用数组时,不仅可以…

PostgreSQL 14.2 安装教程

第一章 PostgreSQL安装 1.1 新建/opt/tools目录 mkdir -p /opt/tools 1.2 上传postgresql文件 1.3 解压postgresql文件 tar -zxvf postgresql-14.2.tar.gz 1.4 进入postgresql并配置 cd postgresql-14.2 mkdir -p /opt/app/postgresql ./configure --prefix/opt/app/postg…

“探索机器学习的多面世界:从理论到应用与未来展望“

博客主页&#xff1a;誓则盟约系列专栏&#xff1a;机器学习 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ 目录 一、机器学习基础理论 1.机器学习的定义与分类 监督学习 无监督学…

英格索兰IC12D3A1AWS-A控制器过热维修

在现代工业生产中&#xff0c;拧紧控制器作为一种自动控制工具&#xff0c;被广泛应用于汽车、航空、电子等领域。然而&#xff0c;在使用过程中&#xff0c;可能会出现IngsollRang拧紧控制器过热故障&#xff0c;影响生产效率和产品质量。 【拧紧设备维修】【英格索兰IngsollR…

js: 百度云BOS 分片上传

百度云BOS存储后怎么查看或下载呢&#xff1f; // 1) 查看登录到百度智能云控制台 – 对象存储BOS”服务–选择一个Bucket&#xff0c;进入后可以查看该Bucket下的所有文件和文件夹。 2&#xff09;下载OS浏览器端不支持批量下载&#xff0c;可以通过以下方式下载文件(使用BOS桌…