C++:二叉搜索树的底层模拟实现


 

概念:

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

搜索二叉树的操作:

int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};
  • 二叉搜索树需要满足左子树比根小,右子树比根大,每一棵树,每一颗子树都需要满足这个条件
  • 二叉搜索树使用中序遍历后,得出的遍历结果一定是一个升序序列
  • 二叉搜索树的的查询操作雷素与二分查找,但是却比二分查找要来的简单,因为搜索二叉树的删除和插入操作比二分查找更为的简单,且并不需要二分查找一样,当插入和删除后必须要在经历一次排序操作。 
  • 最后二叉搜索树是不允许进行节点内部的value 也就是节点数值的修改,这是因为二叉搜索树的排序操作和树的结构是因为节点的value进行链接和形成的

节点结构:

	template<class K>
	//struct BinarySearchTreeNode
	struct BSTreeNode
	{
        
		BSTreeNode<K>* _left;//指向左子树指针
		BSTreeNode<K>* _right;//指向右子树指针
		K _key;//用来进行排序的value数值

		BSTreeNode(const K& key)//节点的初始化列表
			:_left(nullptr)
			, _right(nullptr)
			, _key(key)
		{}
	};

插入操作:

 二叉搜索树的插入 插入的具体过程如下:

a. 树为空,则直接新增节点,赋值给root指针

b. 树不空,按二叉搜索树性质查找插入位置,插入新节点

因为插入时需要保证插入之后还是二叉搜索树,所以插入操作需要进行一次的对比, 例如插入数字5,如果需要插入数字5,那么就需要进行依次的对比!

也就是和每一个节点进行对比,从根节点开始,比对比的节点小往左边移动,比对比的节点大往右移动,直到走到最后一个节点,走向空,需要记住每次插入的节点(非重复)最后都是会插入一个空的位置。

同时还需要注意,当插入的数据已经在树中存在了,而就需要注意搜索树中不会冗余,也就是插入一个相同的元素是不允许在同一个树中出现两次,这是不允许的!除非需要一些扩展。

		bool Insert(const K& key)
		{
			if (_root == nullptr)
			{
                //当根节点是空的时候,直接进行创造新节点进行插入操作
				_root = new Node(key);
				return true;
			}

			Node* parent = nullptr;//记录父节点,方便之后的插入操作
			Node* cur = _root;//从根节点开始进行查找
			while (cur)//当节点为空时表示找到了插入的位置
			{

                //对比 比较的节点 是否比插入的节点的数值大还是小
				if (cur->_key < key)
				{//对比的节点小,则插入的节点往右边进行查找
					parent = cur;
					cur = cur->_right;
				}
				else if (cur->_key > key)
				{//对比的节点大,则插入的节点往左边寻找
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					return false;
				}
			}

			cur = new Node(key);//找到位置后进行插入操作
            //使用之前的父亲节点进行判断,如果比父亲节点大则右边否则左边
			if (parent->_key < key)/
			{
				parent->_right = cur;
			}
			else
			{
				parent->_left = cur;
			}

			return true;
		}

查找函数:

 二叉搜索树的查找:

a、从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。

b、最多查找高度次,走到到空,还没找到,这个值不存在。

bool Find(const K& key)
		{
			Node* cur = _root;//从根节点开始进行查找操作
			while (cur)
			{
				if (cur->_key < key)
				{//查找的节点比比较节点大则往右边进行继续查找
					cur = cur->_right;
				}
				else if (cur->_key > key)
				{//查找的节点比比较节点小则往右边进行继续查找
					cur = cur->_left;
				}
				else
				{//找到了就返回
					return true;
				}
			}
            //直到指向了空就表示二叉搜索树中没有这个节点存在
			return false;
		}

删除函数:

 删除操作分为三种情况/两种操作:

a、删除的节点他的左子树是空的,则他的父节点要指向他的右子树,同理删除的节点他的右子树是空的,他的父节点要指向他的左子树

b、叶子节点,不过叶子节点可以和第一种情况相结合,让父节点指向删除的叶子节点的右子树

c、左右子树都不为空的:需要使用替换法,找该节点的右子树最小节点或者左子树最大节点

这里找右子树的最小节点。

操作一:情况a、情况b

删除的节点他的左子树是空的,则他的父节点要指向他的右子树,同理删除的节点他的右子树是空的,他的父节点要指向他的左子树。

同时这里还会诞生一个问题,被删除节点的左子树是空的,那父节点指向这个节点的右子树,但是是父节点的那一个指针指向呢?是右指针还是左指针?

 需要进行额外的判断,判断删除的节点是父节点的左子树还是右子树,左子树就左指针,右子树就右指针。

且还有一个细节:例如左边为空时,被删除节点的父亲点的要进行判断,判断删除的节点是父亲节点的左节点还是右节点,但是如果我们删除的是根节点,就如上图所示.

这种就需要提前进行判断,如果当前的节点是一个根节点,且左边为空时,那么根节点就需要进行替换,替换成这个根节点的右子树的头一个节点,如果是右边为空,那么这个根节点就需要替换成根节点的左子树的头一个节点。

				else
				{
					// 删除
					// 左为空,父亲指向我的右
					if (cur->_left == nullptr)//判断被删除的节点是否是他的左子树为空
					{
						//if(parent == nullptr)是否为根节点的判断
						if (cur == _root)
						{//为根节点,那么根节点替换成根节点的右子树
							_root = cur->_right;
						}
						else
						{//不算根节点则进行父亲节点的指向判断
							if (cur == parent->_left)
							{
//如果当前节点是父亲节点的左子树,那么就使用父亲节点的左指针指向这个节点的右子树
								parent->_left = cur->_right;
							}
							else
							{
//如果当前节点是父亲节点的右子树,那么就使用父亲节点的右指针指向这个节点的右子树
								parent->_right = cur->_right;
							}
						}

						delete cur;
					}
					else if (cur->_right == nullptr)
					{
						//if(parent == nullptr)//是否是根节点的判断
						if (cur == _root)
						{
							_root = cur->_left;
						}
						else
						{
							// 右为空,父亲指向我的左
							if (cur == parent->_left)
							{
								parent->_left = cur->_left;
							}
							else
							{
								parent->_right = cur->_left;
							}
						}

						delete cur;
					}

操作二:

 左右子树都不为空的:需要使用替换法,找该节点的右子树最小节点或者左子树最大节点,这里找右子树的最小节点。

找到这个被删除节点的右子树,然后进入右子树后一直往左边走,找到被删除节点的右子树的 最小的数,而删除操作则需要吧要删除的节点和最小数替换(交换操作)

但是有产生一个问题,之后要删除的话,可能找不到要删除的节点,所以我们还需要一个最小节点的父亲节点,进行指向操作,所以在找右子树最小节点的时候,还得跟更新他的父节点。

同时删除后让父亲节点的左指针指向空(这里删除的节点是叶子节点没有字节的,所以删除节点的指向是null)

但是这种只能因对常规删除,如果删除的的节点中他的右子树是一个叶子节点呢,又或者,它是根节点呢:

就如上图这种情况,这种情况下删除根节点,那么右子树的最小节点就是根的右孩子节点

同时在我们的代码中,交换删除后,我们让父亲节点的左指针,指向被删除节点的右子树(常规操作是最小右节点的父亲节点指向空,这里的最小右节点刚好是根节点的有孩子),那么放在上图情况会直接崩掉,所以还需要进行一个判断

最后这个判断的前者是判断是否是正常情况,在正常情况中,最小右子树节点的父节点只是一个普通的节点,且最小右子树节点的父节点的左边一定是最小右子树节点,所以可以让父节点指向最小右字数节点的 右子树

而非常情况就是如上图这种情况,根节点的右孩子节点就是根节点的最小右子树节点,这种非常情况下,根节点的最小右子树节点在右边,所以需要让根节点的右指针指向最小右子树节点的 右子树。

else
					{
						// 左右都不为空,替换法删除
						// 
						// 查找右子树的最左节点替代删除
						Node* rightMinParent = cur;
						Node* rightMin = cur->_right;
						while (rightMin->_left)
						{
							rightMinParent = rightMin;
							rightMin = rightMin->_left;
						}

						swap(cur->_key, rightMin->_key);

						if (rightMinParent->_left == rightMin)
							rightMinParent->_left = rightMin->_right;
						else
							rightMinParent->_right = rightMin->_right;

						delete rightMin;
					}
完成的删除函数代码: 
bool Erase(const K& key)
		{
			Node* parent = nullptr;
			Node* cur = _root;
			while (cur)
			{
                 //查询炒作,查询所需要删除的节点的位置
				if (cur->_key < key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (cur->_key > key)
				{
					parent = cur;
					cur = cur->_left;
				}//找到位置后进行删除操作
				else
				{
					// 删除
					// 左为空,父亲指向我的右
					if (cur->_left == nullptr)
					{
						//if(parent == nullptr)
						if (cur == _root)
						{
							_root = cur->_right;
						}
						else
						{
							if (cur == parent->_left)
							{
								parent->_left = cur->_right;
							}
							else
							{
								parent->_right = cur->_right;
							}
						}

						delete cur;
					}
					else if (cur->_right == nullptr)
					{
						//if(parent == nullptr)
						if (cur == _root)
						{
							_root = cur->_left;
						}
						else
						{
							// 右为空,父亲指向我的左
							if (cur == parent->_left)
							{
								parent->_left = cur->_left;
							}
							else
							{
								parent->_right = cur->_left;
							}
						}

						delete cur;
					}
					else
					{
						// 左右都不为空,替换法删除
						// 
						// 查找右子树的最左节点替代删除
						Node* rightMinParent = cur;
						Node* rightMin = cur->_right;
						while (rightMin->_left)
						{
							rightMinParent = rightMin;
							rightMin = rightMin->_left;
						}

						swap(cur->_key, rightMin->_key);

						if (rightMinParent->_left == rightMin)
							rightMinParent->_left = rightMin->_right;
						else
							rightMinParent->_right = rightMin->_right;

						delete rightMin;
					}

					return true;
				}
			}

			return false;
		}

中序遍历:

void InOrder()
		{
			_InOrder(_root);
			cout << endl;
		}
	private:
		void _InOrder(Node* root)
		{
			if (root == nullptr)
			{
				return;
			}

			_InOrder(root->_left);
			cout << root->_key << " ";
			_InOrder(root->_right);
		}

需要注意,这里中序遍历这样写的原因是因为root根节点是私有的原因,所以在外是调不动中序遍历函数的,所以需要把中序遍历函数进行套用,放在私有成员内部,在外部套用一层调用即可使用。 


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

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

相关文章

Leetcode—1235. 规划兼职工作【困难】(upper_bound、自定义排序规则)

2024每日刷题&#xff08;125&#xff09; Leetcode—1235. 规划兼职工作 算法思想 实现代码 class Solution { public:int jobScheduling(vector<int>& startTime, vector<int>& endTime, vector<int>& profit) {int n startTime.size();vec…

循环神经网络完整实现(Pytorch 13)

一 循环神经网络的从零开始实现 从头开始基于循环神经网络实现字符级语言模型。 %matplotlib inline import math import torch from torch import nn from torch.nn import functional as F from d2l import torch as d2lbatch_size, num_steps 32, 35 train_iter, vocab …

(六)SQL系列练习题(下)#CDA学习打卡

目录 三. 查询信息 16&#xff09;检索"1"课程分数小于60&#xff0c;按分数降序排列的学生信息​ 17&#xff09;*按平均成绩从高到低显示所有学生的所有课程的成绩以及平均成绩 18&#xff09;*查询各科成绩最高分、最低分和平均分 19&#xff09;*按各科成绩…

PHP 反序列化

一、PHP 序列化 1、对象的序列化 <?php class people{public $nameGaming;private $NationLiyue;protected $Birthday12/22;public function say(){echo "老板你好呀&#xff0c;我是和记厅的镖师&#xff0c;叫我嘉明就行&#xff0c;要运货吗你&#xff1f;"…

手机恢复出厂设置ip地址会变吗

当我们对手机进行恢复出厂设置时&#xff0c;很多人会担心手机的IP地址是否会发生变化。IP地址对于手机的网络连接至关重要&#xff0c;它决定了手机在网络中的身份和位置。那么&#xff0c;手机恢复出厂设置后&#xff0c;IP地址到底会不会发生变化呢&#xff1f;虎观代理小二…

Jenkins docker部署springboot项目

1、创建jenkins容器 1&#xff0c;首先&#xff0c;我们需要创建一个 Jenkins 数据卷&#xff0c;用于存储 Jenkins 的配置信息。可以通过以下命令创建一个数据卷&#xff1a; docker volume create jenkins_data启动 Jenkins 容器并挂载数据卷&#xff1a; docker run -dit…

Python量化择时的技术指标函数

Python量化择时的技术指标函数 技术指标通过对原始数据&#xff08;开盘价、收盘价、最低价、最高价、成交量、成交金额、成交笔数&#xff09;的处理&#xff0c;来反映出市场的某一方面深层的内涵&#xff0c;这些内涵是很难通过原始数据直接看出来的。技术指标能客观地反映…

EXCEL怎样把筛选后含有公式的数据,复制粘贴到同一行的其它列?

自excel2003版之后&#xff0c;常规情况下&#xff0c;复制筛选后的数据&#xff0c;会忽略隐藏行&#xff0c;仅复制其筛选后的数据&#xff0c;粘贴则是粘贴到连续单元格区域&#xff0c;不管行是在显示状态还是隐藏状态。 一、初始数据&#xff1a; 二、题主的复制粘贴问题…

Codigger数据篇(下):数据安全的全方位保障

在数字化浪潮中&#xff0c;数据已成为现代企业的核心财富。Codigger作为领先的数据服务平台&#xff0c;深知数据安全对于用户的重要性&#xff0c;因此在深挖数据价值的同时&#xff0c;我们始终坚守数据安全防线。 一、双重加密技术保障 Codigger平台运用先进的加密通信和…

C语言学习【最基本】

C语言学习 简单的 C 程序示例 #include "stdio.h" /* 提供键盘输入与屏幕输出支持 */ /* 相当于把stdio.h文件中的所有内容都输入到该行所在位置 拷贝-粘贴 *//* void 表示不带任何参数 */ int main(void) /* 函数名 */ { …

UE—动画

1.动画蓝图 创建动画蓝图 在蓝图中添加状态机 状态机中状态的转换 转换条件设定 播放的动画 使用动画资源 使用混合空间 2.混合空间 混合空间1D 阿赵UE学习笔记——26、动画混合空间_ue 一维动画混合空间-CSDN博客 蓝图创建 混合空间内 按Ctrl到动画节点上即可预览 修改…

1. 傅里叶变换原理

1. 频率域的引入 1.1 时域角度 1.2. 频域角度 不同的角度表达的是同一件事情&#xff0c;从时间域和空间域来进行表达同一间事情 。时间域是都动态的&#xff0c;频率域是静止的 1.3. 时域角度和频域角度 1.4 相位 2 函数的时域角度 2.1 时间域 2.2 频率域 2.3 例子 2.3…

Spring扩展点(一)Bean生命周期扩展点

Bean生命周期扩展点 影响多个Bean的实例化InstantiationAwareBeanPostProcessorBeanPostProcessor 影响单个Bean的实例化纯粹的生命周期回调函数InitializingBean&#xff08;BeanPostProcessor 的before和after之间调用&#xff09;DisposableBean Aware接口在生命周期实例化过…

eSIM Network搭建指南

有任何关于GSMA\IOT\eSIM\RSP\业务应用场景相关的问题&#xff0c;欢迎W: xiangcunge59 一起讨论, 共同进步 (加的时候请注明: 来自CSDN-iot).

redis 缓存一致性,缓存穿透,缓存雪崩,缓存击穿

1.缓存一致性&#xff1a; 缓存一致性就是通过各种方法保证缓存与数据库信息一种&#xff0c;其中最多的办法就是想尽一切办法对过期key进行清除&#xff0c;以保证redis和数据库信息一只&#xff0c;其中就包括了这篇文章中提到的内存淘汰策略&#xff0c;过期key的清除等等&…

MongoDB的分片集群

MongoDB分片技术 介绍 ​ 分片&#xff08;sharding&#xff09;是MongoDB用来将大型集合分割到不同服务器上采用的方法。分片这种说法起源于关系型数据库。但是实际上非关系型数据库在分片方面相比于传统的关系型数据库更有优势。 ​ 与MySQL分库方案对比&#xff0c;MongoDB…

微信小程序之搜索框样式(带源码)

一、效果图&#xff1a; 点击搜索框&#xff0c;“请输入搜索内容消失”&#xff0c;可输入关键字 二、代码&#xff1a; 2.1、WXML代码&#xff1a; <!--搜索框部分--><view class"search"><view class"search-btn">&#x1f50d;&l…

腾讯云IM即时通信引入(React Web端组件式)

开发环境要求 React ≥ v18.0 &#xff08;17.x 版本不支持&#xff09; TypeScript node&#xff08;12.13.0 ≤ node 版本 ≤ 17.0.0, 推荐使用 Node.js 官方 LTS 版本 16.17.0&#xff09; npm&#xff08;版本请与 node 版本匹配&#xff09; chat-uikit-react 集成 …

图像处理ASIC设计方法 笔记21 标记ASIC的顶层状态机

目录 (一)标记ASIC的工作流程1 ASIC首先从控制寄存器内读出待标记图像的基本参数2若写入了有效的启动命令,则进入下面一帧图像的标记过程。3 ASIC通过接口模块从FIFO1中读取待标记的图像4一帧图像初步标记完成后进行等价表的整理压缩5从临时标记存储器中读取临时标记送入标记…

【Github】将github仓库作为图床使用

创建github仓库 首先创建一个github仓库专门用于存储图片&#xff0c;具体步骤如下&#xff1a; 1.点击新的仓库按钮 2.初始配置&#xff1a;随便填写一个仓库名&#xff1b;这里的仓库状态一定要是public公开的&#xff0c;不然后面访问不了图片 下载PicGo PicGo官网 在A…