数据结构9——二叉搜索树

news2025/4/13 17:51:56

🥇1.二叉搜索树的概念

二叉搜索树(Binary Search Tree,BST)又称二叉排序树或二叉查找树,其要么是一棵空树,要么具有以下性质:

①:左子树上所有节点的值都小于根节点;

②:右子树上所有节点的值都大于根节点;

③:它的左右子树也都是二叉搜索树。

(于是我们发现:二叉搜索树的中序遍历是一个递增序列。 

举例:

(易混淆的点:二叉搜索树是要满足左子树中所有节点的值都要小于根,而不是根的直接子节点小于根即可,即:根的左孩子节点,左子树的孙子节点,重孙节点......都要小于根,右子树也是同样的道理。) 

🥇2. 二叉搜索树的实现

使用泛型编程构造出节点和类:

template<class K>
struct BSTreeNode
{
	typedef BSTreeNode<K> Node;

	Node* _left;
	Node* _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;
};

🥈2.1 二叉搜索树的查找

根据二叉搜索树的定义,二叉搜索树的查找无非就是拿待查找节点与根节点比较,比根大从右边找,比根小从左边找,最多查找树的高度次;

当找到空还未找到,则这个值在此二叉搜索树中不存在。

具体实现如下:

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;
	}

🥈2.2 二叉搜索树的插入

二叉搜索树的插入要注意两种情况:

①树本来就是空树,此时可以直接插入;

②树不为空,先查找到要插入的位置,再插入节点。

具体代码如下:

//插入
	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;
	}

🥈2.3 二叉搜索树的删除

二叉搜索树的删除较为复杂,需要分情况讨论:

情况①:要删除的节点没有孩子

情况②:要删除的节点只有左孩子;

情况③:要删除的节点只有右孩子;

情况④:要删除的节点有两个孩子

实际操作中,可以将①②和①③合并,所以代码实现就只有三种情况:

情况①:要删除的节点没有左孩子(此节点只有右孩子或没有孩子);

情况②:要删除的节点没有右孩子(此节点只有左孩子或没有孩子);

①和②可以直接删除目标节点,然后将目标节点的孩子托付给爷爷;

情况③:要删除的节点有两个孩子;

③由于存在两个孩子,所以此时就不能再用“托孤”的方法了,我们可以采用替换法删除:找到一个合适的值换掉要删除的值,然后再删除这个“合适的值”,最后再“托孤”。那么这个所谓“合适的值”该如何寻找呢?到底多合适才算合适呢?

我们知道,二叉搜索树都满足左节点比根小,右节点比根大,换一种说法,根就是这组数据的“中位数”,当删除这个“中位数”时,我们只需再找一个中位数左右两边的值作为新的“中位数”就行了呀!

我们知道二叉搜索树的中序遍历就是一个递增序列,所以:

根左边的值是根的左子树的最右节点(以下图为例,8的左子树的最右节点就是7);

根右边的值是根的右子树的最左节点(以下图为例,8的右子树的最左节点就是10)。

(我们以寻找根的右子树的最左节点作为替换值进行代码实现)

具体代码如下:

//删除
	bool Erase(const K& key)
	{
		//1.先查找要删除的节点
		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;
			}
			//2.查找到要删除的节点
			else
			{
				//①要删除的节点没有左孩子(此节点只有右孩子或没有孩子)
				if (cur->_left == nullptr)
				{
					if (cur == _root)//根节点单独处理
					{
						_root = cur->_right;
					}
					else//只有右孩子,将右孩子托付给爷爷(如上图的节点10)“托孤”
					{
						if (cur == parent->_right)//判断父亲是爷爷的左孩子还是右孩子,以便于继承父亲的位置
						{
							parent->_right = cur->_right;
						}
						else
						{
							parent->_left = cur->_right;
						}
					}

					delete cur;
					return true;
				}
				//②要删除的节点没有右孩子(此节点只有左孩子或没有孩子)
				else if (cur->_right == nullptr)
				{
					if (cur == _root)//根节点单独处理
					{
						_root = cur->_left;
					}
					else//只有左孩子,将左孩子托付给爷爷(如上图的节点14)“托孤”
					{
						if (cur == parent->_right)//判断父亲是爷爷的左孩子还是右孩子,以便于继承父亲的位置
						{
							parent->_right = cur->_left;
						}
						else
						{
							parent->_left = cur->_left;
						}
					}

					delete cur;
					return true;
				}
				//③要删除的节点有两个孩子(如上图的节点3、6、8)
				else
				{
					// 替换法
					// 寻找右子树的最左值作为替换值
					Node* rightMinParent = cur;
					Node* rightMin = cur->_right;
					while (rightMin->_left)
					{
						rightMinParent = rightMin;
						rightMin = rightMin->_left;
					}
					//已找到替换值,开始替换
					cur->_key = rightMin->_key;
					//“托孤”过程
					if (rightMin == rightMinParent->_left)
						rightMinParent->_left = rightMin->_right;
					else
						rightMinParent->_right = rightMin->_right;

					delete rightMin;
					return true;
				}
			}
		}

		return false;
	}

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

		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);
	}
	void InOrder()
	{
		_InOrder(_root);
	}

🥈测试

int main()
{
	BSTree<int> t;
	int a[] = { 3,6,9,1,4,2,7,5,8,10 };
	for (auto e : a)
	{
		t.Insert(e);
	}
	t.InOrder();
	t.Erase(9);
	t.InOrder();
	t.Insert(52);
	t.InOrder();
	return 0;
}

结果如图:

 

🥇3.二叉搜索树的应用

学了二叉搜索树,可是二叉搜索树到底有什么实际的用途呢?接下来就介绍二叉搜索树的两个模型:K模型和KV模型。

🥈3.1 K 模型

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

比如:学校的门禁通过扫脸分辨此人是否为校内人员,具体方式如下:

以每个学生的脸部特征作为Key,构建一棵二叉搜索树;

在二叉搜索树中检索该学生是否存在,存在则开门禁,不存在则不开门禁。

具体实现如下:(为方便演示,这里以学生姓名设置Key,K模型的代码与2中二叉搜索树的实现相似,不同的是需要修改Find函数的返回值和返回类型)

template<class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
public:
	//查找
	Node* 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 cur;
			}
		}
		return nullptr;
	}

private:
	Node* _root = nullptr;
};

int main()
{
	BSTree<string> Stu;
	Stu.Insert("张三");
	Stu.Insert("李四");
	Stu.Insert("王五");
	Stu.Insert("赵六");
	Stu.Insert("田七");
	string str;
	while (cin >> str)
	{
		auto ret = Stu.Find(str);
		if (ret)
		{
			cout << "开门" << endl;
		}
		else
		{
			cout << "保持关闭" << endl;
		}
	}
	return 0;
}

结果如图:

🥈3.2 KV模型

KV模型:每一个关键码key,都有与之对应的值Value,即的键值对。

KV模型相较于K模型更常见,比如:查找英汉词典中英文与中文的对应关系<English,Chinese>;查找学生姓名与性别的对应关系<Name,Sex>。

还以后者为例进行代码实现:

//KV模型
namespace Key_Value
{
	template<class K,class V>
	struct BSTreeNode
	{
		typedef BSTreeNode<K, V> Node;

		Node* _left;
		Node* _right;
		K _key;
		V _value;

		BSTreeNode(const K& key,const V& value)
			:_left(nullptr)
			, _right(nullptr)
			, _key(key)
			, _value(value)
		{}
	};

	template<class K,class V>
	class BSTree
	{
		typedef BSTreeNode<K, V> Node;
	public:
		//查找
		Node* 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 cur;
				}
			}
			return nullptr;
		}

		//插入
		bool Insert(const K& key,const V& value)
		{
			//①树本来就是空树,直接新增节点
			if (_root == nullptr)
			{
				_root = new Node(key, value);
				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, value);
			if (parent->_key < key)
			{
				parent->_right = cur;
			}
			else
			{
				parent->_left = cur;
			}
			return true;
		}

		//删除
		bool Erase(const K& key)
		{
			//1.先查找要删除的节点
			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;
				}
				//2.查找到要删除的节点
				else
				{
					//①要删除的节点没有左孩子(此节点只有右孩子或没有孩子)
					if (cur->_left == nullptr)
					{
						if (cur == _root)//根节点单独处理
						{
							_root = cur->_right;
						}
						else//只有右孩子,将右孩子托付给爷爷(如上图的节点10)“托孤”
						{
							if (cur == parent->_right)//判断父亲是爷爷的左孩子还是右孩子,以便于继承父亲的位置
							{
								parent->_right = cur->_right;
							}
							else
							{
								parent->_left = cur->_right;
							}
						}

						delete cur;
						return true;
					}
					//②要删除的节点没有右孩子(此节点只有左孩子或没有孩子)
					else if (cur->_right == nullptr)
					{
						if (cur == _root)//根节点单独处理
						{
							_root = cur->_left;
						}
						else//只有左孩子,将左孩子托付给爷爷(如上图的节点14)“托孤”
						{
							if (cur == parent->_right)//判断父亲是爷爷的左孩子还是右孩子,以便于继承父亲的位置
							{
								parent->_right = cur->_left;
							}
							else
							{
								parent->_left = cur->_left;
							}
						}

						delete cur;
						return true;
					}
					//③要删除的节点有两个孩子(如上图的节点3、6、8)
					else
					{
						// 替换法
						// 寻找右子树的最左值作为替换值
						Node* rightMinParent = cur;
						Node* rightMin = cur->_right;
						while (rightMin->_left)
						{
							rightMinParent = rightMin;
							rightMin = rightMin->_left;
						}
						//已找到替换值,开始替换
						cur->_key = rightMin->_key;
						//“托孤”过程
						if (rightMin == rightMinParent->_left)
							rightMinParent->_left = rightMin->_right;
						else
							rightMinParent->_right = rightMin->_right;

						delete rightMin;
						return true;
					}
				}
			}

			return false;
		}

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

			_InOrder(root->_left);
			std::cout << root->_key << " ";
			_InOrder(root->_right);
		}
		void InOrder()
		{
			_InOrder(_root);
			std::cout << std::endl;
		}
	private:
		Node* _root = nullptr;
	};
}

int main()
{
	Key_Value::BSTree<string, string> Stu;
	Stu.Insert("张三", "男");
	Stu.Insert("李四", "女");
	Stu.Insert("王五", "男");
	Stu.Insert("赵六", "女");
	Stu.Insert("田七", "女");
	string str;
	while (cin >> str)
	{
		auto ret = Stu.Find(str);
		if (ret)
		{
			cout << ret->_value << endl;
		}
		else
		{
			cout << "查无此人" << endl;
		}
	}
	return 0;
}

结果如图:

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

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

相关文章

leetcode刷题记录(四十八)——128. 最长连续序列

&#xff08;一&#xff09;问题描述 128. 最长连续序列 - 力扣&#xff08;LeetCode&#xff09;128. 最长连续序列 - 给定一个未排序的整数数组 nums &#xff0c;找出数字连续的最长序列&#xff08;不要求序列元素在原数组中连续&#xff09;的长度。请你设计并实现时间复…

c语言——【linux】多进程编程 【进程的创建,相关shell指令,进程状态切换,回收资源,守护进程等】

1.思维导图 2.进程的创建 函数原型&#xff1a;pid_t fork(void); 功能描述&#xff1a;以当前进程为父进程&#xff0c;创建一个子进程 进程链和进程扇的创建 3.多进程具体使用 3.1进程替换 exec 函数一族 int execl(const char *path, const char *arg, ... /* (char *) N…

在服务器上增加新网段IP的路由配置

在服务器上增加新网段IP的路由配置 前提条件步骤一:检查当前路由表步骤二:添加新路由步骤三:验证新路由步骤四:持久化路由配置脚本示例结论在网络管理中,路由配置是一项基本且重要的任务。它决定了数据包在网络中的传输路径。本文将详细介绍如何在服务器上增加新的路由配置…

国产fpga nvme ip高速存储方案设计

国产高速存储方案主要是使用nvme ip实现高速存储方案&#xff0c;nvme ip采用纯verilog语言实现&#xff0c;用户拿到nvme ip使用起来也很简单。 先看看效果如 zu7eg板子&#xff0c;这个芯片支持pcie3.0 x4. zynq 7045板子只支持pcie 2.0 x4 速度测试&#xff0c;测试nvme …

浅谈云计算14 | 云存储技术

云存储技术 一、云计算网络存储技术基础1.1 网络存储的基本概念1.2云存储系统结构模型1.1.1 存储层1.1.2 基础管理层1.1.3 应用接口层1.1.4 访问层 1.2 网络存储技术分类 二、云计算网络存储技术特点2.1 超大规模与高可扩展性2.1.1 存储规模优势2.1.2 动态扩展机制 2.2 高可用性…

[操作系统] 深入理解约翰·冯·诺伊曼体系

约翰冯诺依曼&#xff08;John von Neumann&#xff0c;1903年12月28日—1957年2月8日&#xff09;&#xff0c;原名诺伊曼亚诺什拉约什&#xff08;Neumann Jnos Lajos&#xff09;&#xff0c;出生于匈牙利的美国籍犹太人数学家&#xff0c;20世纪最重要的数学家之一&#xf…

ElasticSearch上

安装ElasticSearch Lucene&#xff1a;Java语言的搜索引擎类库&#xff0c;易扩展&#xff1b;高性能&#xff08;基于倒排索引&#xff09;Elasticsearch基于Lucene&#xff0c;支持分布式&#xff0c;可水平扩展&#xff1b;提供Restful接口&#xff0c;可被任何语言调用Ela…

Qt应用之MDI(多文档设计)

qt creator 版本6.8.0 MinGW 64bit 由此模块可以扩展成设计一个qt文本编辑器。 界面如下 部分功能展示如下 新建文件 打开文件 mdi模式、级联模式和平铺模式 界面和程序构建过程。 1.如图所需.cpp和.h文件 2.mainwindow.ui和tformdoc.ui界面布局如下 不懂什么是Action如何…

【博主推荐】VUE常见问题及解决方案

文章目录 1.找不到模块“../views/index.vue”或其相应的类型声明。ts(2307)2.当改变 Vue 实例中的数据时&#xff0c;视图没有相应地更新3.在某些复杂的异步操作或者多个数据交互场景下&#xff0c;数据绑定的更新在时间上出现延迟4.父组件无法将数据正确地传递给子组件&#…

【Apache Doris】周FAQ集锦:第 29 期

引言 欢迎查阅本周的 Apache Doris 社区 FAQ 栏目&#xff01; 在这个栏目中&#xff0c;每周将筛选社区反馈的热门问题和话题&#xff0c;重点回答并进行深入探讨。旨在为广大用户和开发者分享有关 Apache Doris 的常见问题。 通过这个每周 FAQ 栏目&#xff0c;希望帮助社…

TensorFlow DAY3: 高阶 API(Keras,Estimator)(完)

TensorFlow 作为深度学习框架&#xff0c;当然是为了帮助我们更便捷地构建神经网络。所以&#xff0c;本次实验将会了解如何使用 TensorFlow 来构建神经网络&#xff0c;并学会 TensorFlow 构建神经网络的重要函数和方法。 知识点 Keras 顺序模型Keras 函数模型Keras 模型存储…

【React】脚手架进阶

目录 暴露webpack配置package.json的变化修改webpack.config.js配置less修改域名、端口号浏览器兼容处理处理跨域 暴露webpack配置 react-scripts对脚手架中的打包命令进行封装&#xff0c;如何暴露这些打包配置呢&#xff1f;上篇写到在package.json中的scripts配置项中有eje…

Thrustmaster Hotas Warthog飞行操作杆开发

目录 0 摘 要 &#xff1a;简单说一下这篇文章在搞啥 1 背 景 &#xff1a;什么需求以及对开发的背景调查 2 环境配置 &#xff1a;具体需要什么环境&#xff0c;对软件层面的需求 3 硬件测试 &#xff1a;测试遥感器…

OpenCV基于均值漂移算法(pyrMeanShiftFiltering)的水彩画特效

1、均值漂移算法原理 pyrMeanShiftFiltering算法结合了均值迁移&#xff08;Mean Shift&#xff09;算法和图像金字塔&#xff08;Image Pyramid&#xff09;的概念&#xff0c;用于图像分割和平滑处理。以下是该算法的详细原理&#xff1a; 1.1 、均值迁移&#xff08;Mean …

1.15学习

web ctfhub-网站源码 打开环境&#xff0c;查看源代码无任何作用&#xff0c;但是其提醒就在表面暗示我们用dirsearch进行目录扫描&#xff0c;登录kali的root端&#xff0c;利用终端输入dirsearch -u 网址的命令扫描该网址目录&#xff0c;扫描成功后获得信息&#xff0c;在…

Three.js+Vue3+Vite应用lil-GUI调试开发3D效果(三)

前期文章中我们完成了创建第一个场景、添加轨道控制器的功能&#xff0c;接下来我们继续阐述其他的功能&#xff0c;本篇文章中主要讲述如何应用lil-GUI调试开发3D效果&#xff0c;在开始具体流程和步骤之前&#xff0c;请先查看之前的内容&#xff0c;因为该功能必须在前期内容…

鸿蒙报错Init keystore failed: keystore password was incorrect

报错如下&#xff1a; > hvigor ERROR: Failed :entry:defaultSignHap... > hvigor ERROR: Tools execution failed. 01-13 16:35:55 ERROR - hap-sign-tool: error: Init keystore failed: keystore password was incorrect * Try the following: > The key stor…

Jmeter配置服务代理器 Proxy(二)

1.创建脚本记录器 2.配置&#xff1a;Jmeter代理、端口、记录目标等 3.配置谷歌浏览器代理 浏览器配置代理的详细教程可参考&#xff1a;使用whistle代理-CSDN博客 4.启动Jmeter记录器 点击ok后弹出这个界面&#xff0c;生成了证书&#xff1a; 5.给浏览器安装Jmeter代理的证书…

3.Qt Quick-QML地图引擎之v4.3版本(新增动态轨迹线/海图/天地图街道/天地图卫星)

在上个版本Qt Quick-QML地图引擎之v4版本(新增多模型切换/3D模型欧拉角模拟)_qt加载3d地图-CSDN博客更新了3D模拟功能&#xff0c;在4.3版本增加动态轨迹线、三个地图(海图/天地图街道/天地图卫星)。 4.3版本已经支持qt6 cmake版本&#xff0c;而4.3版本以下支持qt5版本&#x…

我国无人机新增实名登记110.3 万架,累计完成飞行2666万小时

据央视新闻从中国民航局了解到&#xff0c;2024 年我国全年新增通航企业 145 家、通用机场 26 个&#xff0c;颁发无人驾驶航空器型号合格证 6 个、新增实名登记无人机 110.3 万架&#xff0c;无人机运营单位总数超过 2 万家&#xff0c;累计完成无人机飞行 2666 万小时&#x…