二十分钟深入详解<二叉搜索树>!!!

news2024/11/22 11:45:15

目录

前文

一,什么是二叉搜索树?

1.1 二叉搜索树的概念

二, 二叉搜索树的常用操作及其实现

2.1 查找

2.2 插入

 2.3 删除

三,二叉搜索树的应用

3.1 K模型

3.2 KV模型

四,二叉搜索树的性能分析

五,代码

总结


前文

本文主要是带领大家深入了解二叉搜索树的实现以及使用

ps:二叉搜索树的所有代码会在文末贴出(包括构造,析构,赋值等)

一,什么是二叉搜索树?

1.1 二叉搜索树的概念

二叉搜索树又称二叉排序树,它可能是个空树,或是具有以下特征的二叉树

1.若其左子树不为空,则其左子树上的所有值都小于根

2.若其右子树不为空,则其右子树上的所有值都大于根

3.其左右子树也符合二叉搜索树

二叉搜索树作为一种经典的数据结构,它既有链表的快速插入与删除操作的特点,又有数组快速查找的优势;所以应用十分广泛,例如在文件系统和数据库系统一般会采用这种数据结构进行高效率的排序与检索操作。 

二, 二叉搜索树的常用操作及其实现

上图为搜索二叉树的结点结构以及成员变量

2.1 查找

实现思路:

1.从根开始比较,比根大往右走,比根小往左走

2.最多比较树的高度次,走到空还没找到就返回false

 如上图所示,左边为非递归实现,右边是递归实现,这里递归实现需要注意,由于在类中都是通过*this指针调用成员函数,而*this指针是隐藏的无法控制递归条件,所以在类中写递归需要通过子函数完成递归调用。


2.2 插入

实现思路:

1.树为空,则直接new一个新节点给root

2.树不为空,通过二叉树查找的规则找到空位置,插入即可

 代码如下

 2.3 删除

实现思路:

利用二叉树查找的规则找到目标节点,不存在则返回false,但是删除的时候需要考虑以下四种情况

1.目标节点没有左右孩子

2.目标节点有左孩子,没右孩子

3.目标节点有右孩子,没有左孩子

4.目标节点左右孩子都有

 因为只有左孩子或者只有右孩子的处理方式都可以处理左右孩子都没有的情况,所以我们将左右孩子都没有的处理方式和只有左孩子的处理方式合并

处理方式如下:

2.删除该节点,并且被删除节点的父节点指向被删除节点的左孩子——直接删除

 3.删除该节点,并且被删除节点的父节点指向被删除节点的右孩子——直接删除

 4.找到能够替代其位置的值,也就是要满足左子树小于该值,右子树大于该值,我们可以找左子树的最大值或者右子树的最小值,然后被删除节点赋值成找到的最大值或最小值,在删除最大值或最小值节点。

 

 如上图左边是非递归写法,右边是递归写法,有看不懂的可以私信我

三,二叉搜索树的应用

3.1 K模型

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

实际运用:

给一个单词,判断该单词是否拼写正确,具体方式:

具体方式如下:

1.以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
2.在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误

3.2 KV模型

每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生活中非常常见:

1.比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;

2.再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对

四,二叉搜索树的性能分析

在二叉搜索树中查找是绕不过去的功能,无论是插入还是删除都必须先查找,因此查找的效率和二叉搜索树的性能直接挂钩

 尽管是同一组序列,但是不同的插入顺序构造出的搜索二叉树也是不一样的,如上图所示,左边是比较正常的树,右边是极端情况下的歪脖子树。

最优情况下,也就是类似于左边情况,复杂度就是树的高度,也就是:logN

最差情况下,类似于右边的歪脖子树,此时树的高度接近于N,因此其复杂度就是:N、

那么针对右边的歪脖子树有什么优化方法呢?

肯定是有的,后续学习的AVL树和红黑树就会起到极大的效果

五,代码

//K模型
namespace key
{
	template<class K>
	struct BSTreeNode
	{
		BSTreeNode(K key)
			:_left(nullptr)
			, _right(nullptr)
			, _key(key)
		{}

		BSTreeNode<K>* _left;
		BSTreeNode<K>* _right;
		K _key;

	};

	template<class K>
	class BSTree
	{
		typedef BSTreeNode<K> Node;
	public:
		//构造函数
		BSTree()
			:_root(nullptr)
		{}
		//拷贝构造函数,用copy函数前序遍历拷贝
		BSTree(BSTree<K>& t)
		{
			_root = copy(t._root);
		}
		Node* copy(Node* root)
		{
			if (root == nullptr)
				return nullptr;

			Node* ret = new Node(root->_key);
			copy(root->_right);
			copy(root->_left);
			return ret;

		}

		//赋值重载
		BSTree<K>& operator=(BSTree<K> t)
		{
			swap(_root, t._root);
			return *this;
		}

		//析构函数,调用Destory后续遍历销毁即可
		~BSTree()
		{
			Destory(_root);
		}
		void Destory(Node*& root)
		{
			if (root == nullptr)
				return;

			Destory(root->_right);
			Destory(root->_left);
			delete root;
			root = nullptr;

		}
		 
		//查找
		bool Find(const K& key)
		{
			if (_root == nullptr)
				return false;

			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;
		}
		//递归查找
		bool Findr(const K& key)
		{
			_Findr(_root, key);
		}
		bool _Findr(Node*& root, const K& key)
		{
			if (root == nullptr)
			{
				return false;
			}
			if (root->_key < key)
			{
				return _Findr(root->_right, key);
			}
			else if (root->_key > key)
			{
				return _Findr(root->_left, key);
			}
			else//找到了
			{
				return true;
			}
			return false;
		}

		//删除(erase)
		bool Erase(const K& key)
		{
			if (_root == nullptr)
				return false;

			Node* cur = _root;
			Node* parent = nullptr;
			//找val的位置
			while (cur)
			{
				if (cur->_key < key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (cur->_key > key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					if (cur->_right == nullptr)//目标位置没有右孩子
					{
						if (cur == _root)//删除根的情况
						{
							_root = cur->_left;
							delete cur;

						}
						else
						{
							if (parent->_left == cur)
							{
								parent->_left = cur->_left;
								delete cur;
							}
							else
							{
								parent->_right = cur->_left;
								delete cur;
							}
						}

					}
					else if (cur->_left == nullptr)
					{

						if (cur == _root)//删除根的情况
						{
							_root = cur->_right;
							delete cur;

						}
						else
						{
							if (parent->_left == cur)
							{
								parent->_left = cur->_right;
								delete cur;
							}
							else
							{
								parent->_right = cur->_right;
								delete cur;
							}
						}

					}
					else
					{
						//我们这里寻找左子树的最大值
						Node* maxLeft = cur->_left;
						Node* pmaxLeft = cur;
						while (maxLeft->_right)
						{
							pmaxLeft = maxLeft;
							maxLeft = maxLeft->_right;
						}
						cur->_key = maxLeft->_key;
						if (pmaxLeft->_left == maxLeft)
						{
							pmaxLeft->_left = maxLeft->_left;
							delete maxLeft;
						}
						else if (pmaxLeft->_right == maxLeft)
						{
							pmaxLeft->_right = maxLeft->_left;
							delete maxLeft;
						}
					}
					return true;
				}
			}
			return false;
		}

		//递归删除
		bool Eraser(const K& key)
		{
			return _Eraser(_root, key);
		}
		bool _Eraser(Node*& root, const K& key)
		{
			if (root == nullptr)
			{
				return false;
			}
			if (root->_key < key)
			{
				return _Eraser(root->_right, key);
			}
			else if (root->_key > key)
			{
				return _Eraser(root->_left, key);
			}
			else
			{
				//找到要删除的位置了
				Node* cur = root;
				if (root->_right == nullptr)
				{
					/*Node* cur = root;*/
					root = root->_left;
					/*delete cur;*/
				}
				else if (root->_left == nullptr)
				{
					/*Node* cur = root;*/
					root = root->_right;
					/*delete cur;*/

				}
				else//左右子树都存在
				{
					Node* pcur = cur;
					cur = cur->_left;

					while (cur->_right)//找左树最大值
					{
						pcur = cur;
						cur = cur->_right;
					}
					root->_key = cur->_key;

					/*if (pcur->_left == cur)
					{
						pcur->_left = cur->_left;
					}
					else if (pcur->_right==cur)
					{
						pcur->_right = cur->_left;
					}*/
					return _Eraser(root->_left, root->_key);

				}
				delete cur;
				return true;
			}
			return false;
		}

		//插入,成功插入返回true,失败也就是已有所要插入的数字则返回false
		bool Insert(const K& key)
		{
			if (_root == nullptr)
			{
				_root = new Node(key);
			}
			else
			{
				Node* cur = _root;
				Node* parent = nullptr;
				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;
		}
		//递归插入
		bool Insertr(const K& key)
		{
			return _Insertr(_root, key);
		}
		bool _Insertr(Node*& root, const K& key)
		{
			if (root == nullptr)
			{
				root = new Node(key);
				return true;
			}

			if (root->_key < key)
			{
				return _Insertr(root->_right, key);
			}
			else if (root->_key > key)
			{
				return _Insertr(root->_left, key);
			}
			return false;
		}


		//中层遍历
		void InOrder()
		{
			_Inorder(_root);
			cout << endl;
		}
		void _Inorder(Node* root)
		{
			if (root == nullptr)
				return;

			_Inorder(root->_left);
			cout << root->_key << " ";
			_Inorder(root->_right);
		}
	private:
		Node* _root = nullptr;
	};
}

总结

本篇文章主要讲解二叉树的基本概念,实现以及应用场景,希望铁子们能有所收货

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

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

相关文章

SolidWorks建模|渲染装饰件

使用SOLIDWORKS软件建模是许多工程师的选择&#xff0c;对于SOLIDWORKS渲染&#xff0c;也有很多问题&#xff0c;接下来&#xff0c;我们就用SOLIDWORKS建模渲染装饰件的例子来讲解。 1.点击“插入”-“曲面”-“拉伸曲面”&#xff0c;选择上视基准面作为草绘平面&#xff0…

CC2564CRVMR无线音频解决方案、ADE9000ACPZ模拟前端 (AFE) 电路图【MX66L2G45GXRI00 2Gb】FLASH - NOR

CC2564CRVMR 双模蓝牙控制器是一个完整的Bluetooth BR、EDR和低能耗HCI解决方案&#xff0c;可减少设计工作量并缩短上市时间。CC2564C器件基于TI的第七代蓝牙内核&#xff0c;提供符合蓝牙4.2标准的产品验证解决方案。当与微控制器单元 (MCU) 耦合时&#xff0c;该HCI器件具有…

Unity与Andriod交互错误合集

一、无法调用安卓中的方法no non-static method with name‘’ 报错如下&#xff0c;。在保证代码中的方法名没有问题&#xff0c;并且调用的方法名的返回值和传递的参数等都没有问题的情况下&#xff0c; 第一、查看在Unity项目中jar包存放的位置是否正确&#xff0c;需要放在…

LeetCode算法小抄 -- 经典图论算法 之 并查集算法

LeetCode算法小抄 -- 经典图论算法 之 并查集算法 经典图论算法并查集算法动态连通性思路平衡性优化路径压缩Union Find 算法[130. 被围绕的区域](https://leetcode.cn/problems/surrounded-regions/)[990. 等式方程的可满足性](https://leetcode.cn/problems/satisfiability-o…

2023-spring 2.探险营地 — 字符串

&#x1f34e;道阻且长&#xff0c;行则将至。&#x1f353; &#x1f33b;算法&#xff0c;不如说它是一种思考方式&#x1f340; 算法专栏&#xff1a; &#x1f449;&#x1f3fb;123 一、&#x1f331;2023-spring 2.探险营地 题目描述&#xff1a;探险家小扣的行动轨迹&a…

SAM Segment Anything

https://arxiv.org/pdf/2304.02643v1.pdf 包含三个主题&#xff1a;Task、Model、Data Task&#xff08;任务&#xff09;&#xff1a; 需要定义一个 足够通用的图像分割任务&#xff0c;可以提供一个强大的预训练目标&#xff0c;并支持广泛的下游应用程序。 Model&#xf…

mybatis的参数处理详解

mybatis的参数处理详解 parameterType配置参数 1、参数的使用说明 使用标签的 parameterType 属性来设定。该属性的取值可以是基本类型&#xff0c;引用类型&#xff08;例如:String 类型&#xff09;&#xff0c;还可以是实体类类型&#xff08;POJO 类&#xff09;。同时也…

C++学习:类和对象(上)

类和对象 这是C这样的面向对象的语言具有的特性&#xff0c;相较于C语言来说&#xff0c;更加方便的去编写代码&#xff0c;调用代码。 当需要大量重复的调用同一个函数的时候&#xff0c;我们每创建一个函数&#xff0c;就会建立一个栈帧&#xff0c;这样对于空间来讲不友好…

【南京大学PA】 PA0 环境配置 lab (vim | gcc | lab)

本文章学习NJU 的 PA 课程记的笔记 南大PAWLS空间管理 注意事项 whoami 指令 显示你当前的用户IDsudo whoami linux配置 cpp 环境 apt-get install build-essential # build-essential packages, include binary utilities, gcc, make, and so on apt-get install man …

高效办公——Excel表格-03篇(Excel常用快捷键 以及 Excel快捷键结合公式的各种常见的办公例子)

高效办公——Excel表格-03篇&#xff08;Excel常用快捷键 以及 Excel快捷键结合公式的各种常见的办公例子&#xff09; 1. commandE&#xff08;Windows系统&#xff1a;ctrlE&#xff09;——快速分列/重组2. 高效复制的快捷键2.1 command D&#xff08;快速复制上一单元格的…

面试篇:MySQL

一、如何定位慢查询 1、慢查询原因&#xff1a; 聚合查询多表查询表数据量过大查询深度分页查询 表现&#xff1a;页面加载慢、接口无响应&#xff0c;或者响应时间过长&#xff08;超过1s&#xff09; 2、如何定位慢查询 3、面试官&#xff1a;MySQL中&#xff0c;如何定…

Leetcode-day4【88】【167】【125】【345】

文章目录 88. 合并两个有序数组题目解题思路解题思路【学习】尾插入法 167. 两数之和 II - 输入有序数组题目解题思路 125. 验证回文串题目解题思路 345. 反转字符串中的元音字母题目解题思路 88. 合并两个有序数组 题目 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums…

Nacos封装通用HttpClient

一、Nacos下Http请求设计众多处理模块处理&#xff0c;包括更新、Prometheus监控等众多功能&#xff0c;Nacos对这块做了统一封装&#xff0c;扩展性也很好&#xff0c;有新旧版本和同步和异步版本. 二、具体封装模块. 1、引入依赖如下. <dependency><groupId>co…

C++ STL学习之【反向迭代器】

✨个人主页&#xff1a; 夜 默 &#x1f389;所属专栏&#xff1a; C修行之路 &#x1f38a;每篇一句&#xff1a; 图片来源 A year from now you may wish you had started today. 明年今日&#xff0c;你会希望此时此刻的自己已经开始行动了。 文章目录 &#x1f307;前言&a…

[持续更新]mac使用chatgpt的几种方法~

1. monica 使用edge浏览器或者chrome浏览器&#xff0c;直接在官网下载即可&#xff0c;网址直通&#xff1a; bing: https://www.microsoft.com/zh-cn/edge/download?formMA13FJ google&#xff1a; Google Chrome 网络浏览器 备注&#xff1a;你需要先搭上梯子哈 安装打…

【数据库多表操作】sql语句基础及进阶

常用数据库&#xff1a; 数据库&#xff08;Database&#xff09;是按照数据结构来组织、存储和管理数据的仓库&#xff0c;它是长期存储在计算机内、有组织、有结构的数据集合。数据库是信息系统的核心部分&#xff0c;现代软件系统中大量采用了数据库管理系统&#xff08;DBM…

Windows下 influxdb 数据库安装和简单使用

步骤 1&#xff1a;安装 InfluxDB 你可以从 InfluxDB 的 InfluxDB官网winndows二进制安装包下载适用于不同操作系统的 InfluxDB 安装包。在本教程中&#xff0c;我们将介绍在 Windows上安装 InfluxDB 的步骤。 如果所示&#xff0c;可以点击下载windows版本的安卓版&#xff…

VSCode编译器环境下,基于vite+vue调试Cesium

VSCode编译器环境下&#xff0c;基于vitevue调试Cesium 1.创建一个vite项目 以官网作为参考&#xff1a;创建项目 # npm 6.x npm create vitelatest my-vue-app --template vue# npm 7, extra double-dash is needed: npm create vitelatest my-vue-app -- --template vue#…

https页面加载http资源的解决方法

文章目录 1.报错如图2.项目背景3.网上的解决方案4.我的最终解决方案 1.报错如图 2.项目背景 我们的项目采用的全是https请求&#xff0c;而使用第三方文件管理器go-fastdfs&#xff0c;该文件管理器返回的所有下载文件的请求全是http开头的&#xff0c;比如http://10.110.38.25…

计算机组成原理/数据库补充 存储器第四章---虚拟内存

刚刚数据库下课讲了很多有关虚拟内存的东西感觉很多都忘了&#xff0c;现在写这篇文章来复习一下 为什么要引入虚拟内存 在计算机系统中&#xff0c;多个进程共享CPU和内存&#xff0c; 如果太多的进程需要过多的内存空间&#xff0c;那么其中一部分进程就会无法或得足够得空…