二叉搜索树(Binary Search Tree)的深入剖析:代码+画图 详解插入、查找、删除操作

news2024/12/28 19:50:10

BSTree

  • 0 引言
  • 1 二叉搜索树的概念
  • 2 创建一棵二叉搜索树(插入操作)
    • 2.1 画图分析插入操作
    • 2.2 代码思路
    • 2.3 利用中序遍历验证
  • 3 二叉搜索树的查找操作
  • 4 二叉树搜索树的删除操作(重点)
    • 4.1 代码的一些细节分析
  • 5 总结

0 引言

本篇文章会在VS2019下以代码+图片的方式一步一步分析二叉搜索树的插入、删除、查找操作。文章的重点会放在删除操作上。

1 二叉搜索树的概念

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

若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
它的左右子树也分别为二叉搜索树

如图所示是一棵二叉搜索树:
在这里插入图片描述
值得一提的是,因为二叉搜索树独特的性质,我们对它进行中序遍历后,就会发现得到的结果是已经排序好的。所以也叫二叉排序树

如上图,中序遍历后的结果是:

0 1 2 3 4 5 6 7 8 9

2 创建一棵二叉搜索树(插入操作)

首先,我们要定义出树的结点的结构体

template <class K>
struct BSTreeNode //定义结点的结构体
{
	BSTreeNode<K>* left;
	BSTreeNode<K>* right;
	K _key;

	BSTreeNode(const K& key)//初始化
		:left(nullptr), right(nullptr), _key(key)
	{}
};

然后开始设计二叉搜索树的结构

template <class K>
class BSTree
{
	typedef BSTreeNode<K> Node;//节点名称
public:
  //插入,删除,查找操作函数写在这
private:
	Node* root = nullptr;//初始化根结点为空
};

接下来就是利用插入操作来创建出一棵二叉搜索树
我们初始数据就以这幅图为例
在这里插入图片描述

int a[] = { 5,3,7,1,4,6,8,0,2,9 };

2.1 画图分析插入操作

插入的规则就是,要插入的结点从根结点开始比较,比当前结点大就往右继续比较,比当前结点小就往左比较,直到比较到空为止。如果当前结点等于我要插入的值,就不能再继续比较了,因为二叉搜索树不允许有重复的元素。
在这里插入图片描述
首先,插入的时候,原先数组的数据的顺序是会影响到二叉搜索树的结构的。因为我们是按照数组的从左往右遍历插入的。
这里一开始root为空,会先创建一个5结点。
然后按照数组顺序,创建一个3结点,由于3比5小,所以3结点会在5结点的左子树
然后继续准备插入7结点,由于7比5大,所以7结点会在5结点的右子树。
以此类推,完整的二叉搜索树如下

在这里插入图片描述

2.2 代码思路

插入操作的代码思路很简单,几个判断语句就搞定了。
需要注意的是,我们比较到空的时候,需要记录上一个结点才能插入,否则无法和上一个结点链接上。

bool insert(const K& key)//插入成功返回true,插入失败返回false,遇到重复元素就会返回false
	{
		if (root == nullptr)//如果一开始为空,该树就没有结点,创建一个新结点就即可
		{
			root = new Node(key);
			return true;
		}
		else//一开始不为空,需要插入的元素从根结点开始比较了
		{
			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//如果相等的话就返回false,因为不允许有重复元素
				{
					return false;
				}
			}
			//代码走到这里说明cur为空了,我们利用这个cur创建一个结点
			cur = new Node(key);
          //比较要插入的元素和上一个结点的大小
          //比上一个结点大就插入右边
			if (key > parent->_key)
			{
				parent->right = cur;
			}
			else//否则插入左边
			{
				parent->left = cur;
			}
		}
		return true;//代码走到这里说明插入成功
	}

2.3 利用中序遍历验证

这是写在类里面的中序遍历代码
这里有个小技巧,我们嵌套了函数,方便调用的时候可以不用传参

	void _InOrder(Node* root)//中序遍历
	{
		if (root == nullptr) return;

		_InOrder(root->left);
		cout << root->_key << " ";
		_InOrder(root->right);
	}
	void InOrder()//要调用的函数
	{
		_InOrder(root);
	}

代码执行结果如下:
在这里插入图片描述

3 二叉搜索树的查找操作

查找操作和刚才的插入操作思路差不多,都是从根结点开始比较,不过查找操作是找到该元素为止或者走到空为止。

比如,我们现在要插入2这个元素,2比5小往左子树走,2比3小,往左子树走,2比1大,往右子树走,这时候找到了2这个元素,停止比较。
在这里插入图片描述

直接上代码:

bool find(const K& key)
	{
		if (root == nullptr) return false;//如果根结点为空,就返回false,表示未找到

		Node* cur = root;
		while (cur)//从根结点比较
		{
			if (cur->_key < key)//要查找的元素比当前元素大,就往右子树走
			{
				cur = cur->right;
			}
			else if (cur->_key > key)//否则就往左子树走
			{
				cur = cur->left;
			}
			else//相等,返回true,说明找到了
			{
				return true;
			}
		}
		return false;//走到这里说明cur为空了还未找到,二叉搜索树里没有该结点,直接返回false
	}

测试代码如下:
在这里插入图片描述
在这里插入图片描述

4 二叉树搜索树的删除操作(重点)

二叉搜索树的重头戏是删除操作,因为删除了一个结点,可能会影响到多个结点,也可能不会影响。接下来,我们分类讨论。

1.被删除的结点是叶节点

这种情况比较简单,因为在叶节点上,删除后不会影响到其他结点,我们直接删除即可。

如图,我们要删除9这个叶结点,直接删除即可。
在这里插入图片描述

2.被删除的结点只有一个孩子

如果被删除的结点只有一个孩子,我们直接让被删除的结点的父结点领养这个孩子即可。
之所以能被领养,是因为一个结点能管理两个孩子,所以这种思路是可行的。
如图,我们要删除8这个结点,肯定是不能直接删除,因为它还有一个孩子,此时让8的父节点领养这个孩子即可。
在这里插入图片描述

3.被删除的结点有左右两个孩子

被删除的结点有两个孩子的话,就不能像情况2那样找父节点领养了。
那么我们可以想想二叉搜索树的性质:左结点比当前结点小,右节点比当前结点大,且子树也满足此规则。那么也就是说,左子树的所有元素都比当前结点小,右子树的所有元素都比当前结点大。

那么,如果我此时要删除3这个结点,是不是可以用3的左子树里最大的结点或者3的右子树里最小的结点替代掉3。
得益于二叉搜索树这种特殊的性质,很容易验证得到:是可以的。
在这里插入图片描述

3结点左子树的最大值替换3

在这里插入图片描述

3结点右子树的最小值替换3

在这里插入图片描述

4.1 代码的一些细节分析

我们先假设已经找到了要删除的结点,为cur

先来看情况1和情况2,如果是叶结点直接删除和如果要删除的结点只有一个孩子,就找父节点领养该孩子。
那么可以统一处理,把叶节点链接的nullptr也看作孩子。那么就统一成左为空和右为空的情况了。

这里要处理这两种情况

1.如果要删除的是根结点的话,这个根结点在这里肯定只有左子树或者右子树,我们直接拿根节点的下一个非空结点当根结点即可。
在这里插入图片描述
在这里插入图片描述

2.如果不是非根结点,直接按照上面的规则即可,记录要删除结点的父节点,然后领养该结点的孩子

2.1 左为空的情况,如果要删除9结点的话,那么让parent->right = cur->right即可
在这里插入图片描述
或者是删除8结点的话,也是让parent->right = cur->right
在这里插入图片描述
2.2 右为空的情况,我们要删除7这个结点,让parent->right = cur->left

在这里插入图片描述
注意:需要判断cur是parent的左节点还是右节点

			    if (cur->left == nullptr)//如果cur右不为空
				{
					if (cur == root)//如果要删除的结点是根
					{
						root = cur->right;//直接用根的右结点替换根
					}
					else//如果要删除的结点不是根
					{
						if (parent->left == cur)//记录的父节点的左节点是当前要删除的结点
						{
							parent->left = cur->right;//直接让父节点领养
						}
						else//记录的父节点的右结点是当前要删除的结点
						{
							parent->right = cur->right;//直接让父节点领养
						}
					}
					delete cur;//删除cur
				}
				else if (cur->right == nullptr)//右为空同理
				{
					if (cur == root)
					{
						root = cur->left;
					}
					else
					{
						if (parent->left == cur)
						{
							parent->left = cur->left;
						}
						else
						{
							parent->right = cur->left;
						}
					}
					delete cur;
				}

我们最后来看一下情况3,要删除的结点有左右两子树

我们这里采用右子树的最小结点要替换要删除的结点。那么我们需要找到该结点右子树的最小结点。

Node* ppminRight = cur;//cur为要删除的结点,记录最小结点的父节点
Node* minRight = cur->right;//找cur右子树最小的结点
while (minRight->left)//如果minRight的左节点为空就停下
{
	ppminRight = minRight;
	minRight = minRight->left;
}

我们来看以下这两种情况
1.要删除7结点,此时minRight是存在的,minRight要替换掉cur,但是minRight还有自己的右子树,此时我们让父结点领养即可,ppminRight->left = minRight->right
在这里插入图片描述

在这里插入图片描述
2.要删除5结点,此时minRight为7,是5这个结点的右子树的最小值,也是ppminRight的右节点
此时让ppminRight->right = minRight->right

在这里插入图片描述

cur->_key = minRight->_key;//找到了以后就直接交换值即可
if (ppminRight->left == minRight)//判断minRight是否ppminRight的左子树
{
	ppminRight->left = minRight->right;//ppminRight领养minRight的右子树
}
else//否则就就让ppminRight领养minRight的右子树
{
	ppminRight->right = minRight->right;
}
delete minRight;

完整代码:

	bool erase(const K& key)
	{
		Node* parent = nullptr;
		Node* cur = root;
		while (cur)
		{
			if (cur->_key < key)
			{
				cur = cur->right;
			}
			else if (cur->_key > key)
			{
				cur = cur->left;
			}
			else
			{
				if (cur->left == nullptr)
				{
					if (cur == root)
					{
						root = cur->right;
					}
					else
					{
						if (parent->left == cur)
						{
							parent->left = cur->right;
						}
						else
						{
							parent->right = cur->right;
						}
					}
					delete cur;
				}
				else if (cur->right == nullptr)
				{
					if (cur == root)
					{
						root = cur->left;
					}
					else
					{
						if (parent->left == cur)
						{
							parent->left = cur->left;
						}
						else
						{
							parent->right = cur->left;
						}
					}
					delete cur;
				}
				else
				{
					Node* ppminRight = cur;
					Node* minRight = cur->right;
					while (minRight->left)
					{
						ppminRight = minRight;
						minRight = minRight->left;
					}
					cur->_key = minRight->_key;
					if (ppminRight->left == minRight)
					{
						ppminRight->left = minRight->right;
					}
					else
					{
						ppminRight->right = minRight->right;
					}
					delete minRight;
				}
				return true;
			}
		}
		return false;
	}

5 总结

以上就是二叉树搜索树的插入、查找、删除操作。完整的代码如下,读者可自行测试。
如有错误,欢迎指出。
https://gitee.com/F_F_G/structure/blob/master/BinaryTree/BinaryTree/BTree.h

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

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

相关文章

【Elasticsearch】集群部署

文章目录 本地集群Windiows创建 elasticsearch-cluster 文件夹&#xff0c;在内部复制三个 elasticsearch 服务修改集群文件目录中每个节点的 config/elasticsearch.yml 配置文件启动集群测试集群-查看集群状态 本地开启集群Linux软件下载软件安装创建用户修改配置文件启动软件…

链表及链表的常见操作和用js封装一个链表

最近在学数据结构和算法&#xff0c;正好将学习的东西记录下来&#xff0c;我是跟着一个b站博主学习的&#xff0c;是使用js来进行讲解的&#xff0c;待会也会在文章后面附上视频链接地址&#xff0c;大家想学习的可以去看看 本文主要讲解单向链表&#xff0c;双向链表后续也会…

Java后端:html转pdf实战笔记

目录 1、htmltopdf有什么用&#xff1f; 2、什么是wkhtmltopdf 3、wkhtmltopdf 参数介绍 4、示例项目 5、预览效果 1、htmltopdf有什么用&#xff1f; htmltopdf 是一款基于wkhtmltopdf技术的html转pdf文档java类库&#xff0c;支持html转pdf和url转pdf。 2、什么是wkhtmltopdf…

Renesa 瑞萨 A4M2 移植文件系统FAT32

配置SDIO底层驱动&#xff08;SD card&#xff09; 跳到对应GPIO&#xff0c;进行复用 将Operation Mode配置 SD_MMC 4Bit&#xff0c;系统会自动配置 会到stacks 根据上面提示&#xff0c;解决错误。 解决第一个error mmc配置 mmc 以上SD卡底层配置完成 使用串口作为…

linux rs485功能增加

目录 串口驱动层级结构 485配置流程 dts相关 配置注册 初始化 485收发切换 delay_after_send 目前linux 内核中已经支持了485的实现&#xff0c;但由于底层驱动的支持情况&#xff0c;导致我们采用不同芯片时需要对底层驱动进行修改&#xff0c;以满足内核485的各个回调…

Linux-基本指令2

文章目录 touch&#xff08;新建一个文件&#xff09;whoami(查看当前用户名)概念&#xff1a;1.你是如何看待指令的&#xff1f;2.我们在执行指令之前&#xff0c;我们应该先做什么? 概念&#xff1a;/tree . (树状显示文件夹和文件)rmdir && rmrmdirrm ctrl c通配符…

【MFAC】基于全格式动态线性化的无模型自适应控制(Matlab代码)

例题来源&#xff1a;侯忠生教授的《无模型自适应控制&#xff1a;理论与应用》&#xff08;2013年科学出版社&#xff09;。 &#x1f449;对应书本 4.4 单输入单输出系统(SISO)全格式动态线性化(FFDL)的无模型自适应控制(MFAC) 上两篇博客分别介绍了基于紧格式和偏格式动态线…

软件分享--安卓纯文本记事本软件,支持多记事本与密码

文章目录 软件名字&#xff1a;LS记事本支持多记事本安全性&#xff1a;备份和恢复&#xff1a;搜索功能&#xff1a;显示功能&#xff1a;字体调节&#xff1a;轻量绿色下载地址 软件名字&#xff1a;LS记事本 支持多记事本 安全性&#xff1a; 1.每个记事本支持设置访问密码…

【Elasticsearch】几点核心概念

文章目录 核心概念系统架构分布式集群单节点集群故障转移水平扩容应对故障 路由计算(确定哪个主分片)分片控制&#xff08;确定哪个节点&#xff09;创建个集群如何查看数据呢&#xff1f;写流程读流程更新流程 分片原理倒序索引文档搜索动态更新索引持久化变更 文档分析内置分…

“Lunar Lobster “现已经可以下载

Canonical近日发布了Ubuntu 23.04&#xff08;Lunar Lobster&#xff09;操作系统&#xff0c;这是对其流行的GNU/Linux发行版的一次重大更新&#xff0c;带来了一些最新和最伟大的技术和开源软件。 被称为 “Lunar Lobster”&#xff0c;Ubuntu 23.04由最新的Linux 6.2内核系列…

【计算机网络】学习笔记:第五章 传输层【王道考研】

基于本人观看学习b站王道计算机网络课程所做的笔记&#xff0c;不做任何获利 仅进行交流分享 特此鸣谢王道考研 若有侵权请联系&#xff0c;立删 如果本篇笔记帮助到了你&#xff0c;还请点赞 关注 支持一下 ♡>&#x16966;<)!! 主页专栏有更多&#xff0c;如有疑问欢迎…

【数据结构与算法】哈希—— 位图 | 布隆过滤器 | 哈希切割

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《数据结构与算法》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 哈希是一种映射思想&#xff0c;这里再讲解两种应用哈希思想的数据结构。 哈希—— 位图 | …

边学边记——数据结构☞堆(包括优先级队列的介绍)

目录 一、堆&#xff08;二叉堆&#xff09; 堆是用来干什么的&#xff1f; 堆是用什么存储结构存储的&#xff1f; 1. 堆的概念 2. 堆的性质 3. 堆的存储方式 下标之间的规则&#xff1a; 4. 堆的基本操作 (1). 向下调整/堆化操作 (2).堆的创建 (3).堆的插入 (4…

2023-04-30:用go语言重写ffmpeg的resampling_audio.c示例,它实现了音频重采样的功能。

2023-04-30&#xff1a;用go语言重写ffmpeg的resampling_audio.c示例&#xff0c;它实现了音频重采样的功能。 答案2023-04-30&#xff1a; resampling_audio.c 是 FFmpeg 中的一个源文件&#xff0c;其主要功能是实现音频重采样。 音频重采样是指将一段音频数据从一个采样率…

duubo+zookeeper

1、Dubbo简介 1. Dubbo是什么&#xff1f; 高性能、轻量级、开源、基于java Dubbo 是阿里集团开源的远程服务调用的分布式框架&#xff08;告别Web Service模式中的WSDL&#xff0c;以服务者与消费者的方式在dubbo上注册&#xff09; 协议和序列化框架都可以插拔是及其鲜明…

【办公类-22-04】周计划系列(4)-生成“周计划”(提取旧docx指定段落的内容,写入EXCLE模板,再次生成新docx)

代码相当复杂&#xff0c;操作很繁琐&#xff0c;自己都要研究半天T_T 文件夹展示 01提取提取新表的已有内容&#xff08;提取大8班、大7班的新版本里面的额内容&#xff09; &#xff08;需要里面的一些反思&#xff0c;用来占位&#xff09; 这里有一份根据新模板用Python批…

spring2:创建和使用

目录 1.创建Spring项目 1.1创建Maven类 1.2添加Spring支持框架 1.3添加启动类 2.存储Bean对象 2.0 spring项目中添加配置文件(第一次) 2.1创建Bean 2.2把Bean注册到容器中 3.获取并使用Bean对象 3.1创建上下文 3.2获取指定Bean对象 getBean()方法 --> 获取什么…

双周赛103(模拟、网格图BFS、树状数组)

文章目录 双周赛103[6406. K 个元素的最大和](https://leetcode.cn/problems/maximum-sum-with-exactly-k-elements/)模拟 [6405. 找到两个数组的前缀公共数组](https://leetcode.cn/problems/find-the-prefix-common-array-of-two-arrays/)模拟 [6403. 网格图中鱼的最大数目](…

Java 基础进阶篇(一)——— static 静态关键字与单例模式

文章目录 一、static 静态关键字1.1 静态成员变量与实例成员变量1.2 静态成员方法与实例成员方法1.3 static 访问注意事项1.4 内存使用情况 二、工具类三、代码块四、单例模式4.1 饿汉单例4.2 懒汉单例 一、static 静态关键字 static&#xff1a;代表静态的意思&#xff0c;可…

【博学谷学习记录】超强总结,用心分享丨人工智能 AI项目 collate_fn函数理解与记录

目录 Dataloader取数据过程使用报错&#xff1a;默认collate_fn处理不同长度的数据自定义collate_fn伪代码示例 Dataloader取数据过程 取出大小等同于batch size的index列表;将列表列表中的index输入到dataset的getitem()函数中&#xff0c;取出该index对应的数据;对每个index…