【C++】——二叉搜索树(详解)

一  二叉搜索树概念

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

✨若它的左子树不为空,则左子树上所有节点的值都小于根节点的值

✨若它的右子树不为空,则右子树上所有节点的值都大于根节点的值

✨它的左右子树也分别为二叉搜索树

二  二叉搜索树的操作 

和其他结构差不多,一般都为增删查改

2.1  二叉搜索树的查找

✨从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。
✨最多查找高度次,走到到空,还没找到,这个值不存在

比如我们要查找6,那么就会先跟8比较,然后往左边走,比3大,就往右边走,然后就找到了所对应的值。

2.2  二叉搜索树的插入

插入的具体过程如下:
a. 树为空,则直接新增节点,赋值给root指针
b. 树不空,按二叉搜索树性质查找插入位置,插入新节点

 对于插入来说,也就是比查找多了一个插入的过程

2.3  二叉搜索树的删除

对于删除来说就很有讲究了,因为你要保证它删除完以后还是一个二叉搜索树,不然就扯蛋了

  ✨首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情况:
1. 要删除的结点无孩子结点
2. 要删除的结点只有左孩子结点
3. 要删除的结点只有右孩子结点
4. 要删除的结点有左、右孩子结点


看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程
如下:
情况2:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点--直接删除
情况3:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点--直接删除
情况4:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点
中,再来处理该结点的删除问题--替换法删除

2.3 二叉搜索树的应用

K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到
的值。
比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
        以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
        在二叉搜索树中检存在则拼写正确,不存在则拼写错误。

2. KV模型:每一个关键码key,都有与之对应索该单词是否存在,的值Value,即<Key, Value>的键值对。该种方式在现实生活中非常常见:
         比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英
         文单词与其对应的中文<word, chinese>就构成一种键值对;
         再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出
         现次数就是<word, count>就构成一种键值对。

对于容器map和set的底层就是二叉搜索树的优化,所以二叉搜索的应用是非常广泛的。

2.4  二叉搜索树的性能

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。

其实从表面上看二叉搜索树的效率是很高的,但是它也有极端情况

最优情况是第一个,类似完全二叉树,平均比较次数为lon(N),

最坏情况是第二个图,二叉搜索树退化为单支树(或者类似单支)

如果是第二个那么就让搜索二叉树没有了优势,但是后面会有对这个的改良。

三  二叉搜索树的实现

3.1  二叉搜索树结构

首先我们肯定得有一个结点用来存储内部结构

template<class T>
class BSTreeNode
{
	BSTreeNode<T> *left;//左指针
	BSTreeNode<T> *right;//右指针
	T val;

	BSTreeNode(const T&key):val(key),left(nullptr),right(nullptr)
	{
	}
};

其次我们需要的一颗树,对于二叉搜索树来说,我们主要是删除和插入的操作,其他的其实都大同小于

template <class T>
class BSTree
{
	typedef BSTreeNode<T> Node;
    public:
    bool  insert(const T&key);
    bool  Find(const T&key);
    bool  Erase(const T&key);
    private:
    Node* root;
};

3.2   插入元素

对于插入元素,我们需要的是先找到那个可以插入的值,如果要插入的值已经存在,那么就插入失败,如果不存在,那么我就必须按照二叉搜索树的规则进行插入

如果不懂,那看看代码就应该会清晰了

bool insert(const T& key)
	{
		if (root == nullptr)//特判,如果是跟,那么直接插入不需要去找
		{
			root = new Node(key);
			return true;
		}
		Node* cur = root;
		Node* parent = nullptr;//以便我们找到父亲结点
		while (cur)
		{
			if (key>cur->val)//如果插入值大于当前值,往右走
			{
				parent = cur;
				cur = cur->right;
			}
			else if (key < cur->key)//同理,往左走
			{
				parent = cur;
				cur = cur->left;
			}
			else//相等,说明已经存在了,插入失败
			{
				return false;
			}
		}

        //这里说明找到位置了进行插入
		cur = new Node(key);
		if (parent -> val<key)//插入之前也要判断是插入到左边还是右边
		{
			parent->right = cur;
		}
		else
		{
			parent->left = cur;
		}
		return true;
	}

以上就是插入的代码,总结下来就是,先特判,然后去找位置,找到位置进行插入,插入的时候也要判断是插左边还是右边

3.3  寻找元素

寻找元素就是插入元素的简单版本,思路就是插入元素上面的,下面给出代码

bool Find(const T& key)
	{
		if (root == nullptr) return false;
		Node* cur = root;
		Node* parent = nullptr;
		while (cur)
		{
			if (key > cur->val)
			{
				cur = cur->right;
			}
			else if (key < cur->val)
			{
				cur = cur->left;
			}
			else
			{
				return true;
			}
		}
		return false;
	}

3.4  删除元素

对于删除元素,里面的细节就很多了

情况1:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点--直接删除
情况2:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点--直接删除
情况3:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点
中,再来处理该结点的删除问题--替换法删除

下面我们就根据这些操作一步一步去完成

首先我们肯定是先去寻找要删除的元素在哪里

bool Erase(const T& key)
	{
		if (root == nullptr) return false;
		Node* cur = root;
		Node* parent = nullptr;
		while (cur)
		{
			if (key > cur->key)
			{
				parent = cur;
				cur = cur->right;
			}
			else if (key < cur->key)
			{
				parent = cur;
				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* parent = cur;//这里需要把parent置成cur
					Node* subLeft = cur->right;//这里我们去右边找最小的,也就是右边最左值
					while (subLeft->left)
					{
						parent = subLeft;
						subLeft = subLeft->left;
					}
					swap(cur->val, subLeft->val);//找到了交换
					if (parent->left == subLeft)//然后判断是在父亲的左边还是右边
					{
						parent->left = subLeft->right;//因为是最左边,所以他只会有右子树
					}
					else if (parent->right = subLeft)
					{
						parent->right = subLeft - right;
					}
					delete subLeft;//链接上了,就直接删除
				}
				return true;
			}
		}
		return false;
	}

以上就是二叉搜索树的最关键的操作函数实现,其实不难,注意细节就行了

完整代码

#pragma once
template<class T>
class BSTreeNode
{
	BSTreeNode<T> *left;
	BSTreeNode<T>* right;
	T val;

	BSTreeNode(const T&key):val(key),left(nullptr),right(nullptr)
	{
	}
};

template <class T>
class BSTree
{
	typedef BSTreeNode<T> Node;
public:
	bool insert(const T& key)
	{
		if (root == nullptr)
		{
			root = new Node(key);
			return true;
		}
		Node* cur = root;
		Node* parent = nullptr;
		while (cur)
		{
			if (key>cur->val)
			{
				parent = cur;
				cur = cur->right;
			}
			else if (key < cur->key)
			{
				parent = cur;
				cur = cur->left;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(key);
		if (parent -> val<key)
		{
			parent->right = cur;
		}
		else
		{
			parent->left = cur;
		}
		return true;
	}

	bool Find(const T& key)
	{
		if (root == nullptr) return false;
		Node* cur = root;
		Node* parent = nullptr;
		while (cur)
		{
			if (key > cur->val)
			{
				cur = cur->right;
			}
			else if (key < cur->val)
			{
				cur = cur->left;
			}
			else
			{
				return true;
			}
		}
		return false;
	}

	bool Erase(const T& key)
	{
		if (root == nullptr) return false;
		Node* cur = root;
		Node* parent = nullptr;
		while (cur)
		{
			if (key > cur->key)
			{
				parent = cur;
				cur = cur->right;
			}
			else if (key < cur->key)
			{
				parent = cur;
				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* parent = cur;
					Node* subLeft = cur->right;
					while (subLeft->left)
					{
						parent = subLeft;
						subLeft = subLeft->left;
					}
					swap(cur->val, subLeft->val);
					if (parent->left == subLeft)
					{
						parent->left = subLeft->right;
					}
					else if (parent->right = subLeft)
					{
						parent->right = subLeft - right;
					}
					delete subLeft;
				}
				return true;
			}
		}
		return false;
	}
	void InOder()
	{
		_InOder(root);
		cout << endl;
	}

private:
	
	bool _EraseR(Node*& root, const K& key)
	{
		if (root == nullptr) return;
		if (root->val < key)
		{
			return _EraseR(root->right, key);
		}
		else if (root->val > key)
		{
			return _EraseR(root->left, key);
		}
		else
		{
			//删除
			if (root->left == nullptr)
			{
				Node* del = root;
				root = root->right;
				delete del;
				return true;
			}
			else if (root->right == nullptr)
			{
				Node* del = root;
				root = root->left;
				delete del;
				return true;
			}
			else
			{
				Node* subLeft = root->right;
				while (subLeft->left)
				{
					subLeft = subLeft->left;
				}
				swap(subLeft->val, root->val);
				return _EraseR(root->right, key);
			}
		}
	}



	void _InOder(Node*root)
	{
		if (root == nullptr)
			return;
		_InOder(root->left);
		cout << root->val << ' ';
		_InOder(root->right);
	}
	Node* root=nullptr;
};

四  二叉搜索树递归实现

对于二叉搜索树的递归实现,这里着重去解释删除操作的递归

bool _EraseR(Node*& root, const K& key)
	{
		if (root == nullptr) return;
		if (root->val < key)
		{
			return _EraseR(root->right, key);/递归去找删除的数
		}
		else if (root->val > key)
		{
			return _EraseR(root->left, key);
		}
		else
		{
			//删除
			if (root->left == nullptr)//这里不需要特判根结点,因为用了引用,不需要父亲结点 
                                      //自然也不会出现空的情况
			{
				Node* del = root;
				root = root->right;
				delete del;
				return true;
			}
			else if (root->right == nullptr)
			{
				Node* del = root;
				root = root->left;
				delete del;
				return true;
			}
			else
			{
				Node* subLeft = root->right;
				while (subLeft->left)
				{
					subLeft = subLeft->left;
				}
				swap(subLeft->val, root->val);
				return _EraseR(root->right, key);//这里递归去右树删除,这样就省去很多麻烦
			}
		}

从上面代码中我们可以看出,其实少了很多的判断,尤其是父亲结点的链接判断,之所以会这样,就是因为我们用了引用这样可以直接改变指针指向的位置,在连接的时候,就直接连接上了,自然就不需要父亲结点。

有了上面删除的递归版本,那么其他的理解起来就很容易了

bool _InsertR(Node*& root, const K& key)
	{
		if (root == nullptr)
		{
			root = new Node(key);
			return true;
		}

		if (root->val < key)
			return _InsertR(root->right, key);
		else if (root->val > key)
			return _InsertR(root->left, key);
		else
			return false;
	}

	bool _FindR(Node* root, const K& key)
	{
		if (root == nullptr)
		{
			return false;
		}

		if (root->val < key)
		{
			return _FindR(root->right, key);
		}
		else if (root->val > key)
		{
			return _FindR(root->left, key);
		}
		else
		{
			return true;
		}
	}

五 总结

以上就是搜索二叉树的大概内容了,希望对你有用

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

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

相关文章

使用python下载图片且批量将图片插入word文档

最近有一个小的功能实现&#xff0c;从小某书上下载指定帖子的图片们&#xff0c;然后批量插入到word文档中&#xff0c;便于打印。于是有了以上需求。 一、下载图片 1、首先获取图片们的链接img_urls 首先&#xff0c;获取到的指定帖子的所有信息可以存入一个json文件中&am…

Ubuntu/Linux SSH 端口转发

文章目录 Ubuntu/Linux SSH 端口转发概述本地端口转发场景一场景二 参考资料 Ubuntu/Linux SSH 端口转发 概述 SSH, Secure Shell 是一种在网络上用于安全远程登录到另一台机器的工具。除了远程登录以外&#xff0c;ssh 的端口转发是它的另一项强大功能。通过 ssh 端口转发功…

【会议征稿,ACM出版】2024年图像处理、智能控制与计算机工程国际学术会议(IPICE 2024,8月9-11)

2024年图像处理、智能控制与计算机工程国际学术会议&#xff08;IPICE 2024&#xff09;将于2024年8月9-11日在中国福州举行。本届会议由阳光学院、福建省空间信息感知与智能处理重点实验室、空间数据挖掘与应用福建省高校工程研究中心联合主办。 会议主要围绕图像处理、智能控…

颠覆传统编程:用ChatGPT十倍提升生产力

我们即将见证一个新的时代&#xff01;这是最好的时代&#xff0c;也是最坏的时代&#xff01; 需求背景 背景&#xff1a; 平时会编写博客&#xff0c;并且会把这个博客上传到github上&#xff0c;然后自己买一个域名挂到github上。 我平时编写的博客会有一些图片来辅助说明的…

已解决javax.management.BadStringOperationException异常的正确解决方法,亲测有效!!!

已解决javax.management.BadStringOperationException异常的正确解决方法&#xff0c;亲测有效&#xff01;&#xff01;&#xff01; 目录 问题分析 出现问题的场景 报错原因 解决思路 解决方法 分析错误日志 检查字符串值合法性 确认字符串格式 优化代码逻辑 增加…

物联网技术-第6章-物联网应用案例

目录 1.共享单车 2.自动驾驶汽车 &#xff08;1&#xff09;概念 &#xff08;2&#xff09;关键技术 &#xff08;3&#xff09;典型代表 3.智能电网 4.智能交通 &#xff08;1&#xff09;车联网 &#xff08;2&#xff09;无人驾驶 5.智能物流 6.致谢 1.共享单车…

【Oracle】实验一 安装和使用Oracle数据库

【实验目的】 掌握Oracle软件安装过程&#xff0c;选择安装组件掌握建立Oracle数据库&#xff0c;配置网络连接使用SQL*Plus&#xff0c;登录到实例和数据库掌握命令方式的关闭和启动实例及数据库 【实验内容】 安装Oracle19c&#xff0c;记录安装过程。切记&#xff1a;创建…

Vue与SpringSecurity认证整合-06

Vue与SpringSecurity整合 我们要知道springsecurity是一个安全框架,我们在后端的时候没有接触前端,springsecurity引入依赖之后,启动项目会对我们进行拦截,让我们登录,然后我们制定了一个登录页面,也是后端的,我们可以指向我们的登录页面,但是与Vue整合之后,登录页面肯定是在Vu…

古文字识别笔记

前置知识 部件&#xff1a;大部分的汉字是由若干组笔画结构拼合而成的&#xff0c;这些相对独立的笔画结构称为「部件」。 部件是大于基本笔画&#xff08;例如&#xff1a;点、横、撇、捺等&#xff09;而小于或等同于 偏旁 的结构单位。 例如「测」字有三个部件&#xff1a;…

代码阅读器--Understand

代码阅读器--Understand 1 介绍2 安装步骤2.1 下载连接2.2 正常安装&#xff0c;设置自己的安装路径2.3 修改 understand.exe&#xff0c;搜索"areYouThere" &#xff0c; 用"IamNotHere!" 替代2.4 字节序替换 3 使用参考 1 介绍 Understand 的强大不言而…

mysql中存储过过程和游标的联合使用

1.SQL如下&#xff1a; DELIMITER // DROP PROCEDURE IF EXISTS PrintAllEmployeeNames5; CREATE PROCEDURE PrintAllEmployeeNames5() BEGINDECLARE error_count INT DEFAULT 0;DECLARE num INT ;DECLARE done INT DEFAULT 0;DECLARE id1 BIGINT DEFAULT 0;DECLARE address VA…

小柴带你学AutoSar系列一、基础知识篇(6)车规级MCU入门RH850

flechazohttps://www.zhihu.com/people/jiu_sheng 小柴带你学AutoSar总目录https://blog.csdn.net/qiansh

前端核心框架Vue指令详解

目录 ▐ 关于Vue指令的介绍 ▐ v-text与v-html ▐ v-on ▐ v-model ▐ v-show与v-if ▐ v-bind ▐ v-for ▐ 前言&#xff1a;在学习Vue框架过程中&#xff0c;大家一定要多参考官方API &#xff01; Vue2官方网址https://v2.cn.vuejs.org/v2/guide/ ▐ 关于Vue指令的…

python---OpenCv(二),背景分离方法较有意思

目录 边界矩形 旋转矩形(最小外接矩形): 计算轮廓 找4个点的坐标 把浮点型转为Int 画轮廓 边界矩形--&#xff08;最大外接矩形&#xff09; 转灰度 找轮廓 找顶点 画矩形 显示 背景分离方法&#xff08;这个很好玩&#xff0c;可以识别在动的物体&#xff09; 边…

八爪鱼现金流-028,个人网站访问数据统计分析,解决方案

个人网站访问数据统计分析&#xff0c;解决方案 调研 结论&#xff1a;使用百度统计 步骤 1.注册百度统计 2.获取安装代码 3.在项目中&#xff0c;页面代码添加如下片段 <script>var _hmt _hmt || [];(function() {var hm document.createElement("script&…

第10关:视图1 、第11关:视图2 、第12关:用户。

目录 第10关&#xff1a;视图1 任务描述 知识补充 答案 第11关&#xff1a;视图2 任务描述 知识补充 答案 第12关&#xff1a;用户 任务描述 知识补充 答案 本篇博客声明&#xff1a;所有题的答案不在一起&#xff0c;可以去作者博客专栏寻找其它文章。 第10关&…

《Python 机器学习》作者新作:从头开始构建大型语言模型,代码已开源

ChatGPT狂飙160天&#xff0c;世界已经不是之前的样子。 更多资源欢迎关注 自 ChatGPT 发布以来&#xff0c;大型语言模型&#xff08;LLM&#xff09;已经成为推动人工智能发展的关键技术。 近期&#xff0c;机器学习和 AI 研究员、畅销书《Python 机器学习》作者 Sebastian …

计算机网络 动态路由OSPF

一、理论知识 1.OSPF基本概念 ①OSPF是一种链路状态路由协议&#xff0c;使用Dijkstra算法计算最短路径。 ②OSPF使用区域&#xff08;Area&#xff09;来组织网络&#xff0c;区域0&#xff08;Area 0&#xff09;是主干区域。 ③路由器通过通告直连网络加入OSPF域。 ④反…

自制HTML5游戏《贪吃蛇》

一、游戏简介 贪吃蛇是一款经典的电子游戏&#xff0c;最早在1976年由Gremlin公司推出&#xff0c;名为"Blockade"。游戏的玩法简单却富有挑战性&#xff0c;玩家控制一条蛇在封闭的场地内移动&#xff0c;通过吃食物增长身体&#xff0c;同时避免撞到自己的身体或场…

element-plus form表单组件之el-date-picker日期选择器组件

el-date-picker日期选择器组件可根据年&#xff0c;月&#xff0c;日期&#xff0c;时间范围来进行选择&#xff0c;可以自定义日期格式&#xff0c;和样式&#xff0c;还提供多种内置事件。 主要属性如下 属性名说明类型可选值默认值model-value / v-model绑定值&#xff0c…