【C++】模拟实现二叉搜索树的增删查改功能

news2024/11/24 19:18:42

在这里插入图片描述
在这里插入图片描述

个人主页:🍝在肯德基吃麻辣烫
我的gitee:C++仓库
个人专栏:C++专栏

文章目录

  • 一、二叉搜索树的Insert操作(非递归)
    • 分析过程
    • 代码求解
  • 二、二叉搜索树的Erase操作(非递归)
    • 分析过程
    • 代码求解
  • 三、二叉搜索树的Find操作
    • 代码求解
  • 四、构造+拷贝构造+析构+赋值重载
    • 节点的代码
    • 构造函数
    • 拷贝构造函数
    • 赋值运算符重载
    • 析构函数
  • 二叉搜索树递归版本
    • 插入操作递归版本
    • 删除操作递归版本
  • 总结



一、二叉搜索树的Insert操作(非递归)

分析过程

假如这里有一棵树,我们需要对这棵树插入一个新的节点:

在这里插入图片描述

  • 假如需要插入16这个节点。

在这里插入图片描述
要分几个步骤进行:
1)先从根节点开始判断待插入节点和根节点谁大,根节点大就往左比较,根节点小了就往右比较。

第一步这个过程需要提前记录节点的父亲。

2)找到待插入位置后,先new一个新的节点;然后判断该节点是在前面记录的父亲节点的左边还是右边,然后连接起来即可。

代码求解

bool _Insert(Node* root, const T& val)
{
	if (root == nullptr)
	{
		root = new Node(val);
		return true;
	}
	Node* cur = _root;
	Node* cur_par = _root;
	//找插入位置
	while (cur)
	{
		if (val > cur->_val)
		{
			cur_par = cur;
			cur = cur->_right;
		}
		else if (val < cur->_val)
		{
			cur_par = cur;
			cur = cur->_left;
		}
		//相同就不能插入
		else
		{
			cout << "无法插入" << endl;
			return false;
		}
	}
	//找到插入位置了,记录父亲
	Node* insNode = new Node(val);
	if (cur_par->_val < val)
	{
		cur_par->_right = insNode;
		return true;
	}
	else
	{
		cur_par->_left = insNode;
		return true;
	}
}

二、二叉搜索树的Erase操作(非递归)

分析过程

以下面这棵树为例:

假如我们要删除7这个节点。
在这里插入图片描述

1)查找该节点是否存在于树中。

2)如果存在,先判断该节点属于下面的哪种类型:

  • 1)删除的节点是叶子节点,直接删除即可。
  • 2)删除的节点只有一个孩子,需要先判断它的孩子是left还是right,然后让该节点的父亲节点指向它的孩子即可。
  • 3)如果删除的节点有leftright两个孩子,需要找一个节点进行替换;来保证这棵树在删除一个节点后还是一棵二叉搜索树。该找哪个节点来替换呢?
    • 1)找删除节点的左子树的最大节点(最右)
    • 2)找删除节点的右子树的最小节点(最左)

找这两个节点的任意一个均可。

在这里可能有个疑问,万一找不到呢?

你放心吧!一定能找到,这是二叉搜索树的特性。

找到该节点后,将该节点与待删除的节点进行交换,然后删除交换后的节点即可。

在上面的例子中,很显然7属于叶子节点,直接删除即可。

需要注意的是:
我们在寻找那个替代节点时,像插入一样,需要记录它的父
亲,这样在删除的时候才能知道删除left孩子还是right孩子。

代码求解

bool _Erase(Node* root,const T& val)
{
	//第一步:先找到要删除的节点
	Node* cur = root;
	Node* cur_parent = cur;
	while (cur)
	{
		if (cur->_val > val)
		{
			cur_parent = cur;
			cur = cur->_left;
		}
		else if (cur->_val < val)
		{
			cur_parent = cur;
			cur = cur->_right;
		}
		//找到了
		//待删除的节点分三种情况
		else
		{
			//1.左右子树为空;2.其中一个子树为空
			if (cur->_left == nullptr)
			{
				//要知道我是父亲的左还是右
				if (cur_parent->_left == cur)
				{
					cur_parent->_left = cur->_right;
				}
				else if (cur_parent->_right == cur)
				{
					cur_parent->_right = cur->_right;
				}

			}

			else if (cur->_right == nullptr)
			{
				//要知道我是父亲的左还是右
				if (cur_parent->_left == cur)
				{
					cur_parent->_left = cur->_left;
				}
				else if (cur_parent->_right == cur)
				{
					cur_parent->_right = cur->_left;
				}

			}

			//3.删除的节点左右都不为空
			else
			{
				//先找替代节点
				//找左子树的最大节点或者右子树的最小节点来替代
				//         最右             最左
				Node* lParent = cur;
				Node* leftMax = cur->_left;
				while (leftMax->_right)
				{
					lParent = leftMax;
					leftMax = leftMax->_right;
				}

				//找到了,进行替换

				swap(cur->_val, leftMax->_val);

				//替换完成后,必须删除该节点,不能用递归删除。
				//因为如果用递归,可能就找不到要删除的节点了

				//这里还要判断leftMax这个替换节点是它父亲的左还是右子节点
				//因为有一种极端情况是,leftMax是在父亲的左边
				if (lParent->_right == leftMax)
				{
					lParent->_right = leftMax->_left;
					//leftMax是左子树的最右节点了,它不会有右孩子,但可能有左孩子
				}

				else if (lParent->_left == leftMax)
				{
					lParent->_left = leftMax->_left;
				}

				cur = leftMax;
			}
			delete cur;
			cur = nullptr;
			return true;
		}
	}
	return false;
}

三、二叉搜索树的Find操作

查找节点过于简单,直接贴代码。

代码求解

bool _Find(Node* root, const T& val)
{
	if (root == nullptr)
	{
		return false;
	}
	Node* cur = _root;

	while (cur)
	{
		if (cur->_val < val)
		{
			cur = cur->_right;
		}
		else if (cur->_val > val)
		{
			cur = cur->_left;
		}
		else
		{
			return true;
		}
	}
	return false;
}

四、构造+拷贝构造+析构+赋值重载

节点的代码

template<class T>
struct BSTreeNode
{
	BSTreeNode(const T& val)
		:_left(nullptr)
		, _right(nullptr)
		, _val(val)
	{}
	BSTreeNode<T>* _left;
	BSTreeNode<T>* _right;
	T _val;
};

构造函数

BSTree()
	:_root(nullptr)
{}

拷贝构造函数

拷贝构造就是将一棵已有的树对每一个节点进行拷贝即可。
这个过程是深拷贝。

由于我们需要将每一个节点都进行拷贝并连接起来。所以这里需要前序遍历的思想处理。

Node* Copy(Node* root)
{
	if (root == nullptr)
	{
		return nullptr;
	}

	Node* Copyroot = new Node(root->_val);

	Copyroot->_left = Copy(root->_left);
	Copyroot->_right = Copy(root->_right);
	return Copyroot;
}

赋值运算符重载

这里的赋值重载可以用现代写法
1)先将原树传给operator=()函数,用生成临时对象的方式传递,然后让被赋值的树的_root与该临时对象树的_root进行交换即可。

BSTree<T>& operator=(BSTree<T> t)
{
	swap(_root, t._root);
	return *this;
}

这样写的好处是:
1)t是一个临时对象,出了作用域会自己调用析构函数进行销毁。
2)_roott._root交换后,原来这棵树会被临时对象销毁。


析构函数

将一棵树的每一个节点进行释放,就需要从下往上进行逐一释放,这个就用到后续遍历的思想。

~BSTree()
{
	Destroy(_root);
}

//后续遍历销毁
void Destroy(Node* root)
{
	if (root == nullptr)
	{
		return;
	}
	Destroy(root->_left);
	Destroy(root->_right);
	delete root;
	root = nullptr;
}

二叉搜索树递归版本

插入操作递归版本

原理与非递归版本是一样的。

最大的区别是,在root的前面加上了一个引用

  • 1)先找到待插入位置
  • 2)进行插入即可。

这里不再需要记录父亲的原因是:

加了引用后,当遇到空节点时,让

root = new Node(val)

这个操作即可,因为当前的root是上一层栈帧的root节点的孩子(不用管是左孩子还是右孩子)

执行完成这个代码后,相当于让上一层栈帧中的root的孩子

指向了一个New出来的节点。这样就完成了插入。

bool _InsertR(Node*& root, const T& val)
{
	if (root == nullptr)
	{
		root = new Node(val);
		return true;
	}
	
	if (root->_val < val)
	{
		_InsertR(root->_right, val);
	}
	else if (root->_val > val)
	{
		_InsertR(root->_left, val);
	}
	//相同不能插入
	return false;
}

删除操作递归版本

删除的过程与非递归版本是一样的。

1)先找到删除的节点。

找到该节点后,该节点同样有三种情况:

  • 1)该节点是叶子节点
  • 2)该节点只有一个孩子
  • 3)该节点有两个孩子(需要找替代节点)

前面两种情况的处理方法是一样的。

2)判断该节点是属于上面三种的哪一种,如果是前面两种,只需要判断该节点的left为空还是right为空即可。

就相应地执行:

root = root->_right;
或者
root = root->_left;

这两个操作即可。
以为当前栈桢的root是上一层栈桢中root的孩子(不用管是做孩子还是右孩子)
这个代码的意思就是:
让上一层栈桢的root的left/right指向当前层栈桢的root的left/right

在这里插入图片描述

bool _EraseR(Node*& root, const T& val)
{
	if (root == nullptr)
	{
		return false;
	}

	if (root->_val < val)
	{
		return _EraseR(root->_right, val);
	}
	else if (root->_val > val)	
	{
		return _EraseR(root->_left, val);
	}
	//找到了
	else
	{
		Node* del = root;
		//同样有三种情况
		//这是因为root是上一个root的left/right的别名
		if (root->_left == nullptr)
		{
			root = root->_right;
		}
		else if (root->_right == nullptr)
		{
			root = root->_left;
		}
		else
		{
			//找到替代的节点
			Node* leftMax = root->_left;
			while (leftMax->_right)
			{
				leftMax = leftMax->_right;
			}
			//找到之后,交换
			swap(leftMax->_val, root->_val);

			return _EraseR(root->_left, val);
			//不能这样
			//return _Erase(leftMax, val);
			//这样不能保证连接关系正确
		}
		delete del;
		return true;
	}
}

总结

本文章讲述了二叉搜索树的增删查改功能,其中有一些细节需要特别注意。

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

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

相关文章

java从入门到起飞(八)——循环和递归

文章目录 Java循环1. 什么是循环&#xff1f;1.1 为什么需要循环&#xff1f;1.2 循环的分类 2. Java中的循环结构2.1 for循环2.2 while循环2.3 do-while循环 3. 循环控制语句3.1 break语句3.2 continue语句 4. 总结 Java递归1. 什么是递归2. 递归的原理3. 递归的实现4. 递归的…

鼠标右键使用VSCode打开文件或文件夹配置

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

算法通关村17关 | 透析跳跃游戏

1. 跳跃游戏 题目 LeetCode55 给定一个非负整数数组&#xff0c;最初位于数组的第一个位置&#xff0c;数组中的每个元素代表你再该位置可以跳跃的最大长度&#xff0c;判断你是否能够达到最后一个位置。 思路 如果当前位置元素如果是3&#xff0c;我们无需考虑是跳几步&#…

04、javascript 修改对象中原有的属性值、修改对象中原有属性的名字(两种方式)、添加对象中新属性等的操作

1、修改对象中原有的属性值 其一、代码为&#xff1a; // 想将 obj 中的 flag 值&#xff0c;根据不同的值来变化(即&#xff1a;修改对象中原有的属性值)&#xff1b; let obj {"port": "port_0","desc": "desc_0","flag&quo…

【LeetCode】一起探究三数之和的奥秘

Problem: 15. 三数之和 文章目录 题目解析算法原理分析排序 暴力枚举 set去重排序 单调性 双指针划分思想 复杂度Code 题目解析 首先我们来分析一下本题的思路 题目说到要我们在一个整数数组中去寻找三元组&#xff0c;而且呢这三个数字所相加的和为0&#xff0c;而且呢这三…

数据复制:构建大规模分布式系统的关键组成部分

数据复制对于构建可靠的大规模分布式系统至关重要。在本期中&#xff0c;我们将探讨常见的复制策略以及选择合适策略的关键因素。 在本期中&#xff0c;我们将以数据库为例进行讨论。请注意&#xff0c;复制不仅适用于数据库&#xff0c;还适用于缓存服务器&#xff08;如Redis…

探索Apache Hive:融合专业性、趣味性和吸引力的数据库操作奇幻之旅

文章目录 版权声明一 数据库操作二 Hive数据表操作2.1 表操作语法和数据类型2.2 Hive表分类2.3 内部表Vs外部表2.4 内部表操作2.4.1 创建内部表2.4.2 其他创建内部表的形式2.4.3 数据分隔符2.4.4 自定义分隔符2.4.5 删除内部表 2.5 外部表操作2.5.1 创建外部表2.5.2 操作演示2.…

BMS电池管理系统——电芯需求数据(三)

BMS电池管理系统 文章目录 BMS电池管理系统前言一、有什么基础数据二、基础数据分析1.充放电的截至电压2.SOC-OCV关系表3.充放电电流限制表4.充放电容量特性5.自放电率 总结 前言 在新能源产业中电芯的开发也占有很大部分&#xff0c;下面我们就来看一下电芯的需求数据有哪些 …

知名农业企业-九三食品选择泛微京桥通构建全程数字化采购管理

九三食品股份有限公司是北大荒集团旗下九三粮油工业集团有限公司的控股子公司&#xff0c;作为九三集团大豆精深加工生产及研发基地&#xff0c;九三食品公司可生产非转基因“九三”牌大豆油等植物油、食品添加剂、胶囊保健品、精油类产品、食用豆粕、豆饼粉和其它产品等七大类…

yo!这里是进程控制

目录 前言 进程创建 fork()函数 写时拷贝 进程终止 退出场景 退出方法 进程等待 等待原因 等待方法 1.wait函数 2.waitpid函数 等待结果&#xff08;status介绍&#xff09; 进程替换 替换原理 替换函数 进程替换例子 shell简易实现 后记 前言 学习完操作…

【更新至2022年】2000-2022年全国31省市以2000年为基期的实际GDP、名义GDP、GDP平减指数数据(含原始数据+计算过程+计算结果)

2000-2022年31省市名义GDP 实际GDP GDP平减指数 1、时间&#xff1a;2000-2022 2、范围&#xff1a;31省市 3、来源&#xff1a;GJ统计J和统计NJ 4、指标&#xff1a;名义GDP、地区生产总值指数&#xff08;上年100&#xff09;、实际GDP&#xff08;以2000年为基期&#x…

【光伏系统】将电流从直流转换为交流电的太阳能逆变器、太阳能跟踪系统来提高系统的整体性能及集成电池解决方案(Simulink仿真)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

(高阶)Redis 7 第10讲 单线程 与 多线程 入门篇

面试题 1.Redis 是单线程还是多线程 最早的版本3.x是单线程。 版本4.x,严格意义不是单线程。负责处理客户端请求的线程是单线程,开始加入异步删除。 6.0.x版本后明确使用全新的多线程来解决问题 2.说说IO多路复用3.Redis 为什么快IO多路复用+epoll函…

基于SSM的乡镇自来水收费系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

【leetcode 力扣刷题】回文串相关题目(KMP、动态规划)

回文串相关题目 5. 最长回文子串动态规划中心扩展算法 214. 最短回文串336. 回文对 5. 最长回文子串 题目链接&#xff1a;5. 最长回文子串 题目内容&#xff1a; 题目就是要我们找s中的回文子串&#xff0c;还要是最长的。其实想想&#xff0c;暴力求解也行……就是遍历所有的…

系统移植MakefileREADME文件分析

Makefile # 指定交叉编译工具链前缀变量 CROSS_COMPILE arm-linux-gnueabihf- #指定文件名字变量 NAME interface ## #-g:编译时添加gdb调试信息 -marm: 将程序编译生成arm指令集 -Wall:编译时显示所有警告信息 #-O0:编译时添加优化等级 -O0:不优化 -O1:一级优化 #-f…

zabbix配置钉钉告警、和故障自愈、监控java

文章目录 1.配置钉钉告警server 配置web界面创建媒介给用户添加媒介测试告警 实现故障自愈功能监控Javazabbix server 安装java gateway配置 Zabbix Server 支持 Java gateway使用系统内置模板监控 tomcat 主机 1.配置钉钉告警 server 配置 钉钉告警python脚本 脚本1 cd /…

实相融、云启未来,智慧公厕让城市生活更美好

现代社会&#xff0c;随着科技的不断发展&#xff0c;人们对于城市生活的要求也在不断提升。在这个过程中&#xff0c;智慧公厕作为城市基础设施中的重要组成部分&#xff0c;正在发挥着越来越重要的作用。通过数字化、云管理、人工智能等未来的科技方式&#xff0c;智慧公厕为…

2024字节跳动校招面试真题汇总及其解答(一)

1. 【算法题】重排链表 给定一个单链表 L 的头节点 head ,单链表 L 表示为: L0 → L1 → … → Ln - 1 → Ln请将其重新排列后变为: L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → … 不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。 示例 1: 输入:hea…

嵌入式Linux驱动开发(同步与互斥专题)(二)

一、自旋锁spinlock的实现 自旋锁&#xff0c;顾名思义&#xff1a;自己在原地打转&#xff0c;等待资源可用&#xff0c;一旦可用就上锁霸占它。 ① 原地打转的是CPU x&#xff0c;以后CPU y会解锁&#xff1a;这涉及多个CPU&#xff0c;适用于SMP系统&#xff1b; ② 对于单…