数据结构(11)——二叉搜索树

news2025/1/14 18:13:16

欢迎来到博主的专栏:数据结构
博主ID:代码小豪


文章目录

    • 二叉搜索树
      • 二叉搜索树的声明与定义
      • 二叉搜索树的查找
      • 二叉搜索树的插入
      • 二叉搜索树的中序遍历
      • 二叉搜索树的删除
    • key_value型搜索二叉树

二叉搜索树

二叉搜索树也称二叉排序树,是具备以下特征的二叉树
(1)每个节点最多拥有两个子节点
(2)对于每个节点,其左子树值均小于根节点,其右子树均大于根节点。

如图:
在这里插入图片描述
该二叉树为二叉搜索树,原因如下:
以节点8为根节点,其左子树均小于8,右子树均大于8.
在这里插入图片描述
以节点3为根节点,其左子树均小于3,其右子树均大于3.
在这里插入图片描述
以此类推。

在这里插入图片描述
该二叉树则不为二叉搜索树,因为以节点7为根节点,节点4小于7,却存在于根节点的右子树,因而不符合右子树均小于根节点这一特征。所以不构成搜索二叉树。

搜索二叉树有个重要的规则就是,在二叉树内不能存在两个相同值的节点,因为这会对二叉搜索树的删除结点造成影响。

二叉搜索树的声明与定义

二叉搜索树可以分为两部分,一部分是节点,另一部分则是树

对于节点,其需要记录一个值,以及分别指向左子节点与右子节点的两个指针。

再设计一个构造函数,通过传递key的值来生成该节点,并将左右指针置为空(方法不唯一,只是博主采用这种构造方式)

template<class k>
struct TreeNode
{
	TreeNode(const k& key)
		:_key(key)
		,_left(nullptr)
		,_right(nullptr)
	{}
	k _key;
	TreeNode* _left;
	TreeNode* _right;
};

接着是设计一个类来控制整个二叉搜索树,只需要定义一个指向二叉树的根节点的指针成员即可。此外,在设计一些搜索二叉树的成员函数,比如查找节点,排序节点,插入节点和删除结点。

template<class k>
class BSTree
{
public:
	typedef TreeNode<k> Node;//将节点类型重命名
	void find(const k& key);//查找
	void insert(const k& key);//插入节点
	void erase(const k& key);//删除结点
	void inorder();//中序遍历
private:
	Node* _root=nullptr;//根节点
};

二叉搜索树的查找

如何在二叉搜索树当中找到值为key的节点呢?当然,直接使用遍历的形式是可行的,但是利用二叉搜索树的特性可以更加高效。

用key值与根节点进行比较,会出现以下三种情况
(1)key比根节点大
(2)key比根节点小
(3)key等于根节点

如果是情况3,那就说明找到key值了,返回查找的结果

若是情况(1),根据二叉搜索树的特征,比根节点大的值都在右子树,因此进入右子树查找

若是情况(2),比根节点小的值都在左子树,因此进入左子树查找。

如果key来到了空节点处,就说明该二叉搜索树没有匹配的节点,因此查找的结果是无。

bool find(const k& key)//查找
{
	Node* cur = _root;
	while (cur != nullptr)
	{
		if (key > cur->_key)//比key小,进入右树
			cur = cur->_right;
		if (key < cur->_key)//比key大,进入左树
			cur = cur->_left;
		else//与可以相等,返回结果
			return true;
	}
	return false;
}

动画演示1:假设key为7
在这里插入图片描述
演示动画2:假设key为9
在这里插入图片描述

其实二叉搜索树的查找方式和二分查找的原理非常类似,都是将查找数据的区间一步步缩短,最后确定元素。

二叉搜索树的插入

在二叉搜索树种插入新节点需要解决一个问题
(1)在插入新节点后,如何保持二叉搜索树的性质?

其实也就是搞清楚新节点插入的位置在哪,其实这个问题的解决思路可以参考前面查找节点的方法。

前面提到,通过缩短数据区间,可以在搜索二叉树中定位到元素的位置,我们可以假设待插入的节点已经存在于二叉树当中。通过查找该节点的方式,定位到元素的位置,然后将该节点插入到对应的位置即可。

思路如下:
(1)通过查找的方式缩短区间(即用key对比根节点)
(2)如果走到了空节点,就说明定位到了元素的位置,直接插入即可
(3)如果存在与插入节点相同值的节点,根据二叉搜索树不能存在两个相同节点的特性,停止不合法的插入操作

还存在一种特殊情况,即当二叉搜索树为空树时,此时无法通过查找的方式进行搜索(因为要让key和根节点的值进行比较,但是空树没有根节点),解决方法是直接生成根节点即可。

bool insert(const k& key)//插入节点
{
	if (_root == nullptr)
	{
		_root = new Node(key);
		return true;
	}
	Node* cur = _root;
	Node* parent = nullptr;
	while (cur != nullptr)
	{
		if (key > cur->_key)//比key小,进入右树
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (key < cur->_key)//比key大,进入左树
		{
			parent = cur;
			cur = cur->_left;
		}
		else//与key相等,不合法的插入操作
			return false;
	}
	//来到空节点处,将新节点插入至该处
	cur = new Node(key);
	if (key > parent->_key)
		parent->_right = cur;
	else
		parent->_left = cur;
	return true;
}

动画演示:
在这里插入图片描述

二叉搜索树的中序遍历

中序遍历二叉搜索树,会得到一个升序的结果,先来写出代码,再分析原理。

void inorder()//中序遍历
{
	_inorder(_root);
}
void _inorder(Node* root)
{
	if (root == nullptr)
		return;
	_inorder(root->_left);
	cout << root->_key<<' ';
	_inorder(root->_right);
}

执行如下操作:

int arr[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
BSTree<int> b;
for (auto e : arr)
{
	b.insert(e);
}

上述代码的作用是构造出下图的二叉搜索树
在这里插入图片描述
再中序遍历该二叉搜索树

b.inorder();

运行结果为:
在这里插入图片描述
那么为什么中序遍历会呈现出升序的特征呢?

根据二叉搜索树的定义,对于每个根节点来说,其左子树小于根节点,其右子树大于根节点。那么下面这个二叉树的排序为
在这里插入图片描述
Ln2<n1<Rn2

在这里插入图片描述
Ln3<Ln2<Rn3<n1<Ln4<Rn2<Rn4.

把小于号去掉,即为
Ln3 Ln2 Rn3 n1 Ln4 Rn2 Rn4.
这个顺序既符合中序遍历的顺序,也符合升序的顺序。我们也可以由此得出一个结论

离根节点越左的节点,越小,离根节点越右的节点,越大,越靠近根节点的节点,越接近根节点。比如上图中的Ln4和Rn3.
在这里插入图片描述
这个结论将会在二叉搜索树的删除中起到非常大的左右。

二叉搜索树的删除

首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情况:

a. 要删除的结点无子结点
在这里插入图片描述
解决方法:直接删除即可。

b. 要删除的结点只有左子结点
在这里插入图片描述
删除该结点且使被删除节点的父结点指向被删除节点的左子结点
在这里插入图片描述

c. 要删除的结点只有右子结点
在这里插入图片描述
删除该结点且使被删除节点的父结点指向被删除结点的右子结点
在这里插入图片描述
看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来。

d. 要删除的结点有左、右孩子结点
在这里插入图片描述

这种情况下是最麻烦的,因为我们不能贸然的将节点3删除,这会断开二叉搜索树与节点3的左右子树的链接,破坏结构。

换句话说,要删除节点3,就要找到一个节点来替换节点3,这样才能保持住二叉搜索树的特性。那么什么节点可以胜任呢?

为了维持二叉搜索树的特性,因此替换节点的节点应该有以下特征:(1)比左子节点大(2)比右子节点小。因此选取来替换的节点应该越接近待删除的节点越好。
在这里插入图片描述
在上一节中提到,越接近根节点的节点,其值也越接近根节点。对于待删除的节点来说,最接近它的值无非两个
(1)其左子树最右边的节点
(2)其右子树最左边的节点
这两个节点都是离待删除节点最近的节点。也是值最接近的节点。因此,选取这两个节点来替换待删除的节点再合适不过了。

删除操作的实现如下:

bool erase(const k& key)//删除结点
		{
			 Node* cur = _root;
			 Node* parent = cur;
			 while (cur != nullptr)
			 {
				 if (key > cur->_key)//比key小,进入右树
				 {
					 parent = cur;
					 cur = cur->_right;
				 }
				 if (key < cur->_key)//比key大,进入左树
				 {
					 parent = cur;
					 cur = cur->_left;
				 }
				 else//与key相等,删除该节点
				 {
					 if (cur->_left == nullptr)//情况a、b
						 if (parent->_left = cur)
							 parent->_left = cur->_right;
						 else
							 parent->_right = cur->_right;
					 else if(cur->_right==nullptr)//情况c
						 if (parent->_left = cur)
							 parent->_left = cur->_left;
						 else
							 parent->_right = cur->_left;
					 else//情况d
					 {
						 Node* RightMinp = cur;//右子树的最左节点的父节点
						 Node* RightMin = cur->_right;//右子树的最左节点
						 while (RightMin->_left != nullptr)
						 {
							 RightMinp = RightMin;
							 RightMin = RightMin->_left;
						 }
						 cur->_key = RightMin->_key;//交换节点
						 if (RightMinp->_right == RightMin)
							 RightMinp->_right = RightMin->_right;
						 else
							 RightMinp->_left = RightMin->_right;
						 delete RightMin;
					 }
					 return true;
				 }
			 }
			 return false;
		}

key_value型搜索二叉树

key_value型搜索二叉树与key型搜索二叉树的不同在于,key型二叉树按照key值将节点进行分流,而key_value型搜索二叉树也是按照key值将节点进行分流,但是每个节点还会有一个对应的value值。

以现实距离,每个人可能会有相同的姓名,但是身份证号一定不同,即身份证是每个人的key,key值不能相同,而value值可以相同,通过在key_value型搜索二叉树查找key值非常快的特性,这样存储数据会变得更加灵活。

key_value型搜索二叉树的插入、删除都相同,不同的点在于key_value型搜索二叉树的节点多了一个value值,因此博主不再赘述对应操作,直接给出源代码。

template<class key,class value>
struct kvtreeNode
{
	kvtreeNode(const pair<key, value>& kv)
		:_kv(kv)
		,_left(nullptr)
		,_right(nullptr)
	{;}

	pair<key, value> _kv;
	kvtreeNode<key, value>* _left;
	kvtreeNode<key, value>* _right;
};

template<class key,class value>
class kvtree
{
public:
	kvtree()
		:_root(nullptr)
	{;}

	kvtree(const initializer_list<pair<key, value>>& list)
	{
		for (const auto& e : list)
		{
			insert(e);
		}
	}

	typedef kvtreeNode<key, value> Node;

	bool insert(const pair<key,value>& kv)
	{
		Node* newnode = new Node(kv);
		if (_root == nullptr)
		{
			_root = newnode;
			return true;
		}
		Node* cur = _root;
		Node* parent = _root;
		while (cur != nullptr)
		{
			if (newnode->_kv.first < cur->_kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (newnode->_kv.first > cur->_kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}

		if (newnode->_kv.first > parent->_kv.first)
			parent->_right = newnode;
		else
			parent->_left = newnode;
		return true;
	}

	bool find(const key& key)
	{
		Node* cur = _root;
		while (cur != nullptr)
		{
			if (key < cur->_kv.first)
				cur = cur->_left;
			else if (key > cur->_kv.first)
				cur = cur->_right;
			else
				return true;
		}
		return false;
	}

	void inorder()
	{
		_inorder(_root);
	}

	bool erase(const key& key)
	{
		assert(_root!=nullptr);
		Node* cur = _root;
		Node* parent = _root;
		while (cur != nullptr)
		{
			if (key < cur->_kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (key > cur->_kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else//找到节点并删除,待删除节点为cur
			{
				if (_root->_left==nullptr&&_root->_right==nullptr)
				{
					delete _root;
					_root = nullptr;
					return true;
				}

				if (cur->_left == nullptr)
				{
					if (parent->_left == cur)
					{
						parent->_left = cur->_right;
					}
					else
					{
						parent->_right = cur->_right;
					}
				}
				else if (cur->_right == nullptr)
				{
					if (parent->_left == cur)
					{
						parent->_left = cur->_left;
					}
					else
					{
						parent->_right = cur->_left;
					}
				}
				else
				{
					//找到右子树的最小节点
					Node* rightmin = cur->_right;
					Node* rightminp = cur;
					while (rightmin->_left != nullptr)
					{
						rightminp = rightmin;
						rightmin = rightmin->_left;
					}
					cur->_kv = rightmin->_kv;

					if (rightminp->_left != rightmin)
					{
						rightminp->_right = rightmin->_right;
					}
					else
					{
						rightminp->_left = rightmin->_right;
					}
					delete rightmin;
					return true;
				}
				delete cur;
				return true;
			}
		}
	}

private:
	void _inorder(Node* root)
	{
		if (root == nullptr)
			return;

		_inorder(root->_left);
		cout << root->_kv.second << " ";
		_inorder(root->_right);
	}
	Node* _root;
};

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

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

相关文章

sourceinsigt + samba

samba服务器 yum install -y sambsystemctl start smb systemctl enable smb 注意 setenforce 0 是一个用于管理 SELinux&#xff08;Security-Enhanced Linux&#xff09;模式的命令。SELinux 是 Linux 内核的一种安全模块&#xff0c;提供了强制访问控制&#xff08;MAC&am…

【密码学】密钥管理:②密钥分配

一、密钥分配的定义 密钥分配是密钥管理生命周期中最重要的部分&#xff0c;密钥分配方案研究的是密码系统中密钥的分发和传送问题。从本质上讲&#xff0c;密钥分配为通信双方建立用于信息加密、解密签名等操作的密钥&#xff0c;以实现保密通信或认证签名等。 &#xff08;1…

埃氏筛选法求素数

埃氏筛选法求素数可以减少遍历次数&#xff0c;及在前期的循环中就将存在的合数打上标记&#xff0c;从而提高算法的时间效率。 一、算法实现 void prime_number(int n) {int flag[n];int count 0;int* primeArr;/// 默认标记所有的数都是素数memset(flag,0,sizeof(flag…

浅析打电话检测算法接打电话识别算法展示及其全套打电话检测算法源码

打手机检测算法&#xff0c;特别是智能边缘分析一体机中的打手机检测算法&#xff0c;是一种专门用于监控和分析在边缘计算设备上的手机使用行为的算法。这种算法主要利用了机器学习和计算机视觉的先进技术&#xff0c;通过对设备上的视频流或图像进行深入分析&#xff0c;以识…

“万物共生”户外沉浸式展馆光影互动设计,思特科技打造!

01      思特科技助力北京玉渊潭公园 “万物共生” 户外沉浸式展馆光影互动设计&#xff0c;将公园独具特色的人文景观和自然景观相结合&#xff0c;利用数字光影艺术&#xff0c;通过不同形式、不同状态来表现生命的多元化&#xff0c;带来震撼的沉浸式体验。    北京…

加密与安全_解密AES加密中的IV和Seed

文章目录 概述IV&#xff08;Initialization Vector&#xff0c;初始化向量&#xff09;Seed&#xff08;种子&#xff09; CodeseedIV 小结 概述 在AES加密中&#xff0c;**IV&#xff08;Initialization Vector&#xff0c;初始化向量&#xff09;和Seed&#xff08;种子&am…

HoloLens 坐标系统 Coordinate systems

Hololens 和 Unity 空间坐标系统-CSDN博客文章浏览阅读79次。这意味着&#xff0c;在 X、Y 或 Z 轴上相距 2 个单位的物体&#xff0c;在混合现实中的渲染效果是相距 2 米。虽然左手坐标和右手坐标是最常见的系统&#xff0c;但 3D 软件中也会使用其他坐标系。例如&#xff0c;…

【杂乱算法】前缀和与差分

前缀和 文章目录 前缀和一维应用 二维差分一维 二维扩展1、前缀和与哈希表 一维 一个数组prefix中&#xff0c;第i个元素表示nums[0]至nums[i-1]的总和&#xff0c;那么我们就称这个prefix数组是nums数组的前缀和。 prefix [ i ] ∑ j 0 i nums [ j ] \text{prefix}[i] \s…

显示弹出式窗口的方法

文章目录 1. 概念介绍2. 使用方法3. 示例代码 我们在上一章回中介绍了Sliver综合示例相关的内容&#xff0c;本章回中将介绍PopupMenuButton组件.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在本章回中介绍的PopupMenuButton组件位于AppBar右侧&#xf…

x-cmd pkg | dua - 一个可以方便地了解给定目录的磁盘空间使用情况的工具

目录 简介用户首次快速实验指南技术特点竞品和相关项目进一步阅读 简介 dua 是 Disk Usage Analyzer 的简写&#xff0c;该工具可以快速查看给定目录的磁盘空间使用情况。 对于想要深入了解磁盘空间使用情况并有效管理存储的用户来说&#xff0c;Dua 是一个很有价值的工具。通…

项目1 物流仓库管理系统

一、项目概述 本项目旨在开发一个功能全面的物流仓库管理系统&#xff0c;以数字化手段优化仓库作业流程&#xff0c;提高管理效率。系统集成了前端用户交互界面与后端数据处理逻辑&#xff0c;涵盖了从用户注册登录、订单管理、货单跟踪到用户信息维护等多个核心业务模块。通…

heic格式转化jpg用什么方法?这8个教程很实用

随着iOS设备的普及&#xff0c;HEIC&#xff08;High Efficiency Image Coding&#xff09;格式成为了默认的图片格式&#xff0c;因其相较于传统的JPEG格式&#xff0c;能更有效地压缩图片文件&#xff0c;保存更多细节&#xff0c;同时占用更少的存储空间。然而&#xff0c;H…

【UE5】Groom毛发系统的基本使用——给小白人添加头发

目录 效果 步骤 一、准备 二、使用3DsMax制作毛发 三、在UE中给小白人安装毛发 四、修改毛发材质 效果 步骤 一、准备 1. 新建一个第三人称模板工程 2. 在项目设置中&#xff0c;勾选“支持计算蒙皮缓存” 3. 在插件面板中&#xff0c;启用“Groom”和“Alembic Gro…

kubernetes的pod基础

kubernetes的pod基础 pod概念 pod&#xff08;豆荚&#xff09;&#xff0c;是k8s的最小管理单元。是一个或多个容器的组合&#xff0c;这些容器共享存储&#xff0c;网络和命名空间&#xff0c;以及运行规范&#xff0c;pod内的容器统一的进行安排和调度。pod是一组具有共享命…

智慧党建系统设计与实现_1i659

TOC springboot629智慧党建系统设计与实现_1i659--论文 研究背景 近年来&#xff0c;由于计算机技术和互联网技术的快速发展&#xff0c;使得所有企事业单位内部都是数字化、信息化、无纸化的发展趋势&#xff0c;随着趋势的发展&#xff0c;各种决策系统、辅助系统也应运而…

Java | Leetcode Java题解之第350题两个数组的交集II

题目&#xff1a; 题解&#xff1a; class Solution {public int[] intersect(int[] nums1, int[] nums2) {Arrays.sort(nums1);Arrays.sort(nums2);int length1 nums1.length, length2 nums2.length;int[] intersection new int[Math.min(length1, length2)];int index1 …

27.移除元素---力扣

题目链接&#xff1a; . - 力扣&#xff08;LeetCode&#xff09;. - 备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/remove-element/description/ 问题描述&#xf…

Maven的简单使用

Maven使用 Maven的作用1. 自动构建标准化的java项目结构(1) 项目结构① 约定目录结构的意义② 约定大于配置 (2)项目创建坐标坐标的命名方法&#xff08;约定&#xff09; 2. 帮助管理java中jar包的依赖(1) 配置使用依赖引入属性配置 (2) maven指令(3) 依赖的范围(4) 依赖传递(…

Vitis AI 进阶认知(Torch量化基础+映射+量化参数+对称性+每通道+PTQ+QAT+敏感性)

目录 1. 介绍 2. 基本概念 2.1 映射函数 2.2 量化参数 2.3 校准 2.4 对称与非对称量化 2.5 Per-Tensor and Per-Channel 2.6 PTQ 2.7 QAT 2.8 敏感性分析 2.6 退火学习率 3. 几点建议 4. 总结 1. 介绍 Practical Quantization in PyTorch | PyTorchQuantization i…

OpenCV几何图像变换(4)亚像素图像截取函数getRectSubPix()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 从图像中以亚像素精度检索像素矩形。 getRectSubPix 函数从 src 中提取像素&#xff1a; p a t c h ( x , y ) s r c ( x center.x − ( dst.…