c++学习之搜索二叉树

news2025/1/13 17:39:36

目录

一,什么是搜索二叉树?

二,搜索二叉树的实现 

非递归实现

节点与类成员

插入

查找

删除

 递归实现

插入

查找

 删除


一,什么是搜索二叉树?

搜索二叉树(Binary Search Tree)是一种常见的二叉树数据结构,它的每个节点都包含一个关键字,且每个节点的关键字都大于其左子树中任意节点的关键字,小于其右子树中任意节点的关键字。

这种特性使得搜索二叉树非常适合用于查找操作。在搜索二叉树中查找一个元素时,我们可以从根节点开始,如果当前节点的值等于要查找的值,则查找成功;如果当前节点的值大于要查找的值,则在左子树中继续查找;如果当前节点的值小于要查找的值,则在右子树中继续查找.

搜索二叉树还有一些其他的性质,例如:对于一棵有n个节点的搜索二叉树,它的高度不会超过log(n) . 这些性质使得搜索二叉树在计算机科学中有着广泛的应用,例如在数据库索引、编译器符号表等领域。

如下就是一个二叉搜索树:

二,搜索二叉树的实现 

这里主要指的是实现而二叉树的增删查,搜索二叉树不能被修改。

非递归实现

根据搜索二叉树的特性,实现也是好比较实现的:

到了删除,这个是比较麻烦的,因为我们不能直接删除这个节点,若为叶子节点还好,但凡这个节点但有两个孩子,删除直接会破坏搜索二叉树的结构,那么如何删除。

用如下的搜索二叉树为例:

情况一:首先对于叶子节点我们找到后可以直接删除

情况二:删除的节点左子树为空,右子树不为空

如这里的节点10,我们可以将它的孩子托付给它的父亲,即链接8节点的right与14节点,删除节点10。

情况三:删除的节点右子树为空,左子树不为空

情况三与情况四的方法类似,还是用它的父亲节点管理它的孩子节点。

情况四:删除的节点右子树,左子树都不为空

如这里删除节点3,上述思路就不行了。

解决办法:替换法   

所谓替换法就是去找一个其他节点来接管我们的孩子,如删除节点3,我么可以看到节点4可以替换3,节点1也可以替换3,虽然他也是这里的子节点,当然节点6理论可以,但它自身也有孩子。

或者删除节点8为例,我们必须要找到一个比8节点左边都大的,且比右边的每个数最小

总结:因此我们可以选择已该节点为起点,它的左子树中最大的节点(左边最大)节点4,右子树中最小(左子树中最右节点)节点7,这两个都可以是我们替换的对象是最为合适的。

节点与类成员

//结点的定义
template<class k>struct BSTreeNOde
{
	BSTreeNOde(const k& key) :_left(nullptr),_right(nullptr)
	{
		this->key = key;
	}
	BSTreeNOde<k>* _left;
	BSTreeNOde<k>* _right;
	k key;
};
template<class k>class BSTree
{
public:
typedef BSTreeNOde<k>  Node;//搜索二叉树的节点
//成员函数......
private:
	Node* _root=nullptr;
}

插入

//节点插入
bool insert(const k& key)
{
	if (_root == nullptr)
	{
		_root = new Node(key);
		return true;
	}
	Node* cur = _root;
	Node* parent = nullptr;
	while (cur)
	{
		if (key < cur->key)
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (key > cur->key)
		{
			parent = cur;
			cur = cur->_right;
		}
		else
		{
			//相等就不枉搜索二叉树里插入了
			return false;
		}
	}
    //cur为空的时候,即找到了合适的位置,就可以插入了
    cur = new Node(key);
	//链接
	if (key > parent->key)
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}
	return true;
}

查找

//查找
bool find(const k key)
{
	if (_root == nullptr)
	{
		return false;
	}
	Node* cur = _root;
	while (cur)
	{
		if (key > cur->key)
		{
			cur = cur->_right;
		}
		else if (key > cur->key)
		{
			cur = cur->left;
		}
		else
		{
			return true;
		}

	}
	return false;
}

删除

对于上述分析的情况,情况一就是情况二或情况三的一种,全空可以为左为空或者右为空。

不为空时,我们这里选择替换右子树中的最小值:

对于删除,我们这里断开和删除节点的链接即可,其次对于为空时,我们这里选择替换右子树中的最小值是分两种情况的:当我们的的右子树的第一个节点的左子树是否为空,不为空,我们去一直找最左边的,左子树为空,那么最小的就是第一个右子树节点。

//删除节点
bool Erase(const k& key)
{
	Node* parent = nullptr;
	Node* cur = _root;
	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 (cur == parent->_right)
					{
						parent->_right = cur->_right;
					}
					else
					{
						parent->_left = cur->_right;
					}
				}
			}
			else if (cur->_right == nullptr)
				//右为空
			{
				if (cur == _root)
				{
					//直接用孩子替换为新的根节点
					_root = cur->_left;
				}
				else
				{
					//直接让它的父亲指向它的右孩子
					if (cur == parent->_right)
					{
						parent->_right = cur->_left;
					}
					else
					{
						parent->_left = cur->_left;
					}
				}
			}
			//左右都不为空
			else
			{
				//对于这里左子树的最大(最右),右子树的最小(最左)这两个节点都可以被我们所替换

				//找右树的最小节点(最左)
				Node* subleft = cur->_right;
				Node* parent = cur;
				if (subleft == nullptr)
				{

					cur->_left;
				}
				while (subleft->_left)
				{
					parent = subleft;
					subleft = subleft->_left;
				}
				swap(cur->key, subleft->key);
				//此时subleft的右子树不一定为空  或者 在寻找前此时的左子树整个为空,用他的右子树链接
				if (subleft == parent->_left)
				{
					parent->_left = subleft->_right;
				}
				else
				{
					parent->_right = subleft->_right;
				}
			}
			return true;
		}
	}
	return false;

}

 递归实现

对于增删查,不仅仅可以通过循环,我们也可以通过递归实现

递归实现时,我们可以直接传root的引用,无需用cur来遍历,不过我们需要内部下一个函数来访问到我们的root。利用root的引用,再增加和删除时,递归到该位置我们可直接改变这个节点。

插入

插入的主要过程就是递归左右子树遍历直到此时的root为空,就可插入,return true。否则false。

bool insertR(const k& key)
	{
		return _insertR(key, _root);
	}
	bool _insertR(const k& key, Node*& root)
	{
		if (root == nullptr)
		{
			//递归直到此时为空,链接
			root = new Node(key);
			return true;
		}
		if (root)
		{
			if (key < root->key)
			{
				return _insertR(key, root->_left);
			}
			else if (key > root->key)
			{
				return _insertR(key, root->_right);
			}
			else
			{
				//相等就不往搜索二叉树里插入了
				return false;
			}
		}
	}

查找

原理同上,递归的左右子树,遍历找我们需要的元素。

bool findR(const k& key)
	{
		return _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;
		}
	}

 删除

因为循环时无法复用我们在当左子树右子树都不为空时,我们去找右子树的最小值,即右树的最左值,循环右数的左子树的最左直到为空,交换之后,开始删除,我们想要服用左子树为空的删除问题,但此时的父亲不一样,我们不能去服用。

但对于递归,当我们要去找右子树的最左孩子,找到之后交换,然后变成子问题递归,左子树为空删除。

bool EraseR(const k& key)
	{
		_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
		{
			// 删除
			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(root->_key, subLeft->_key);
	
				// 转换成在子树去递归删除
				return _EraseR(root->_right, key);
			}
		}

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

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

相关文章

jdbc 执行批处理任务

package com.csdn.jdbc; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; //演示执行批处理任务 public class ExecuteBatchTask {public static void main(String[] args) throws ClassNotFou…

通过jsonobject.tostring 传字符串为有空格问题

目录 通过jsonobject.tostring 传字符串为有空格问题 1.问题原因解决思路解决方案总结参考 文章所属专区 项目问题解决 1.问题原因 通过JSONObject.toString()方法将字符串转换为JSON格式时&#xff0c;可能会出现空格的情况。这是因为JSONObject.toString()方法在生成JSON字…

理解什么是接口测试?怎样做接口测试?

一 什么是接口&#xff1f; 接口测试主要用于外部系统与系统之间以及内部各个子系统之间的交互点&#xff0c;定义特定的交互点&#xff0c;然后通过这些交互点来&#xff0c;通过一些特殊的规则也就是协议&#xff0c;来进行数据之间的交互。接口测试主要用于外部系统与系统之…

贪心算法学习——单调递增的数字

一&#xff0c;单调递增的数字 1.题目 当且仅当每个相邻位数上的数字 x 和 y 满足 x < y 时&#xff0c;我们称这个整数是单调递增的。 给定一个整数 n &#xff0c;返回 小于或等于 n 的最大数字&#xff0c;且数字呈 单调递增 。 示例 1: 输入: n 10 输出: 9示例 2: 输入…

【c++速通】入门级攻略:引用详解 | auto的类型推导 | 不一样的for循环 | nullptr版本空指针

&#x1f3a5; 屿小夏 &#xff1a; 个人主页 &#x1f525;个人专栏 &#xff1a; C入门到进阶 &#x1f304; 莫道桑榆晚&#xff0c;为霞尚满天&#xff01; 文章目录 &#x1f4d1;前言&#x1f324;️引用☁️引用的概念☁️引用的特性⭐引用在定义时必须初始化 ☁️常引用…

javascript IP地址正则表达式

注&#xff1a; 一定不要把表达式赋值给变量&#xff0c;直接表达式.test() /^(1[0-9]{2}|2[0-4][0-9]|25[0-5]|(\d){1,2})\.(1[0-9]{2}|2[0-4][0-9]|25[0-5]|(\d){1,2}|0)\.(1[0-9]{2}|2[0-4][0-9]|25[0-5]|(\d){1,2}|0)\.(1[0-9]{2}|2[0-4][0-9]|25[0-5]|(\d){1,2}|0)$/g.te…

【转载】双亲委派模型

双亲委派模型是 Java 类加载器的一种工作模式&#xff0c;通过这种工作模式&#xff0c;Java 虚拟机将类文件加载到内存中&#xff0c;这样就保证了 Java 程序能够正常的运行起来。那么双亲委派模型究竟说的是啥呢&#xff1f;接下来我们一起来看。 1.类加载器 双亲委派模型针…

驱动开发6 IO多路复用——epoll

核心操作&#xff1a;一棵树、一张表、三个接口 相关案例 #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys…

2022年12月 Python(一级)真题解析#中国电子学会#全国青少年软件编程等级考试

Python等级考试(1~6级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 关于Python语言的注释,以下选项中描述错误的是?( ) A: Python语言有两种注释方式:单行注释和多行注释 B: Python语言的单行注释以#开头 C: Python多行注释使用###来做为标记 D: …

RPA为什么会火起来?

RPA的前世今生 RPA&#xff08;Robotic Process Automation&#xff0c;机器人流程自动化&#xff09;&#xff0c;这项技术旨在模拟人类一系列应用软件中执行规则性的任务技术&#xff0c;通过精准、规范且搞笑的自动化处理过程&#xff0c;极大提高了日常业务开展的效率和准…

同一个页面同一区域两个el-table在v-if下样式重叠问题

&#x1f349;正常情况下在radio切换时两个表格的样式应如下 &#x1f349;实际上用v-if显示时会出现以下问题&#xff08;本该属于时间段相同模块的表格却出现在时间段自定义的表格中&#xff09; &#x1f349;解决方案&#xff1a; &#x1f343;一、将v-if替换成v-show(…

零基础Linux_23(多线程)线程安全+线程互斥(加锁)+死锁

目录 1. 线程安全 1.1 线程不安全前期 1.2 线程不安全原因 2. 线程互斥 2.1 加锁保护&#xff08;代码&#xff09; 2.2 锁的本质 3. 可重入对比线程安全 4. 死锁 4.1 死锁的必要条件 4.2 避免死锁 5. 笔试面试题 答案及解析 本篇完。 1. 线程安全 基于上一篇线程…

计算机网络(谢希仁)第八版课后题答案(第三章)

1.数据链路(即逻辑链路)与链路(即物理链路)有何区别? “电路接通了”与”数据链路接通了”的区别何在? 数据链路与链路的区别在于数据链路出链路外&#xff0c;还必须有一些必要的规程来控制数据的传输&#xff0c;因此&#xff0c;数据链路比链路多了实现通信规程所需要的硬…

​​​​​​​如何解决Google play开发者新注册账号,身份验证的地址证明问题?

我们知道&#xff0c;Google Play应用市场的发展速度惊人&#xff0c;但这两年&#xff0c;为了防止恶意软件的传播&#xff0c;谷歌要求开发者账号需要进行身份验证才能发布应用。 而今年越来越严格&#xff0c;不仅在提审时需要进行电话验证&#xff08;链接&#xff09;&am…

TCP三次握手具体过程

四次挥手 1&#xff09;客户端进程发出连接释放报文&#xff0c;并且停止发送数据。释放数据报文首部&#xff0c;FIN1&#xff0c;其序列号为sequ&#xff08;等于前已经传送过来的数据的最后一个字节的序号加1)&#xff0c;此时&#xff0c;客户端进入FIN_WAIT_1&#xff08…

Echarts柱状图渐变色问题变通

问题背景 设计稿中给出了如下图的效果&#xff0c;在柱状图的最上面给出了一个白色的小块&#xff0c;起初我一直在思考亦或者搜索相关的问题&#xff1a;如何在Echarts柱状图顶部实现一个24*4的白色矩形块。始终不得其解&#xff0c;在一个吃饭的瞬间冒出来一个想法是否可以用…

图像的特征点描述与提取

一、说明 特征点算法是图像处理中主要算法之一&#xff0c;它是图像物体匹配的关键步骤&#xff0c;因此&#xff0c;是个极其重要的题目&#xff0c;至今依旧研究不断&#xff0c;本篇讲述历年来学者在领域研究的突出贡献&#xff0c;即六种不同的特征点提取办法&#xff0c;供…

Python------学生管理(文件txt处理)

项目&#xff1a;Python实现学生管理系统 注&#xff1a;免费源码下载 项目介绍&#xff1a; 功能描述&#xff1a;&#xff08;1&#xff09;添加学生信息&#xff08;2&#xff09;删除学生信息&#xff08;3&#xff09;修改学生信息&#xff08;4&#xff09;查询学生信息…

JVM(Java Virtual Machine)G1收集器篇

前言 本文参考《深入理解Java虚拟机》&#xff0c;本文主要介绍G1收集器的收集思想和具体过程&#xff08;填上一篇文章留下的坑&#xff09; 本系列其他文章链接&#xff1a; JVM&#xff08;Java Virtual Machine&#xff09;内存模型篇 JVM&#xff08;Java Virtual Machi…