C++ 二叉树进阶

news2024/9/20 1:12:05

1.二叉搜索树简介

二叉搜索树又称二叉排序树,它或者是一棵空树 ,或者是具有以下性质的二叉树 :
若它的左子树不为空,则左子树上 所有节点的值都小于根节点的值
若它的右子树不为空,则右子树上 所有节点的值都大于根节点的值
它的左右子树也分别为二叉搜索树
并且二叉搜索树的中序遍历之后是一个有序数组,就是因为先走左边的,但是左边的一定更小;最后再右边的,但是右边的一定最大。
二叉搜索树也叫BinarySearchTree 即 BST

二叉搜索树的功能:

在C语言阶段对树的学习中我们了解到,二叉树用于储存数据或者用于排序并非最优解。

二叉树的主要功能是查找:

理论上,二叉搜索树中不允许有冗余、重复的数据(这里一个4、那里一个4)

默认情况下,搜索二叉树也不支持修改节点中的数据,但是变形之后BST支持冗余或者修改。

2. 二叉树的基本接口

先完成基本结构:

#pragma once
#include <iostream>
#include <assert.h>
#include <vector>
using namespace std;

template<typename K>
class BSTNode {
public:
	typedef BSTNode<K> Node;
	Node* _left;
	Node* _right;
	K _key;//作为数据
};

template<typename K>
class BinarySerachTree {
public:
	typedef BSTNode<K> Node;
protected:
	Node* _root = nullptr;
};

2.1 插入

空容器的第一步是加入数据, 但是因为标准BST不允许冗余元素,

所以插入之前需要先查一下有没有这个元素,先完成一个Find函数:

                          

写完Find之后,再进行插入。

插入的逻辑同Find,找到合适的位置之后插入:

bool Insert(const K& key) {
	Node* newnode = new Node(key);
	//为空单独判断
	if (_root == nullptr) {
		_root = newnode;
		return true;
	}
	//非空树时,找到合适的位置再加入	
	Node* cur = _root;
	Node* parent = _root;
	while (cur) {
		if (cur->_key > key) {
			parent = cur;
			cur = cur->_left;
		}
		else if (cur->_key < key) {
			parent = cur;
			cur = cur->_right;
		}
		else {
			return false;
		}
		//此时已经找到了合适的父节点,但至于向左插入还是向右插入还需要再判断一次
	}

	if (parent->_key > key) parent->_left = newnode;
	if (parent->_key < key) parent->_right = newnode;

	return true;
}

因为最后一遍cur已经走到nullptr了,所以需要再使用一次分支语句判断一下往哪走。


为了检查是否插入,我们还需要一个函数打印一下树,采取中序遍历能直接有序打印:

                 

但是_root作为私有成员,想直接Inorder(_root)是不行的:

还需要套一层封装处理一下:

因为_root是不能被外部访问的,只能套一个内部套一个外部了。


在写删除节点之前,我们再来观察一下搜索二叉树: 

搜索二叉树有查找和去重的作用

也可以排序+查重

查找效率并不是O(logN)

树也有可能退化成:

毕竟没有要求搜索二叉树必须是完全二叉树

所以按照最差的情况,最坏的情况的时间复杂度是O(lN)

可以用平衡二叉搜索树来解决,也就是传说中的AVL树和红黑树


2.2 删除

我们以这棵树为例:

                                        

1.叶子节点很好解决,直接删除即可。比如1和7

2.有一个孩子的,直接托孤即可,把你的孩子交给你的父节点。比如6和14

                                    

3.有两个孩子的,要找人替代。左子树的最大到右子树的最小这个区间的数据都能替代。

但是我们的实际方法就是将这两个节点其中选一个拿去替代。

至于如何找最大或者最小,把根传进去,找小就一直往左走,找大就往右走。

先将二叉树再补充的复杂一点,以删去3为例:

比方说我们用右子树的最左节点去替换(右子树的最左节点和左子树的最右节点一定满足情况或者2,也就是说最多有一个节点或者本身就是叶子节点),替换之后可以直接删除这个 右子树的最左节点 或者左子树的最右节点

并且还有以下规律:

 bst中,最左侧的节点最小,最右侧的节点最大。

这一规律在子树上同样适用。

想要删除,需先找到数据,找到了开始删除:

开始删除时,先讨论第一二种情况,两种情况可以合并,可以把第一种删除叶子节点的情况想象成将nullptr托孤给父节点:

                                 

注意:

1.为了保证能找到父节点,所以还是要用双指针法

2.分类讨论的逻辑:如果要被删除的cur的左为空,那就意味着cur要把右边托孤给父节点;但是该让parent的left去接受还是right去接受呢?所以必须再判断一次cur是parent的左还是右。

最后看两个孩子的情况:

我们先假设都用右子树的最小(左)节点来替换,当然也可以用左子树的最大(右)节点。

先去右子树找小(左d)节点:

只有left还存在,就一直往左走:

然后交换数据,将替代者的数据给到cur的位置去:

完成这一步之后就希望删除rightMin节点了。

不过想删除rightMin,必须要用到他的parent

                           

所以还是必须双指针跟着走,所以我们创造了变量rightMinP

                   

我们还是使用之前的加强树作为测试用例:

                            

假设我们要删除的是3:

没有问题,那我们执行一下全部删除:

结果在删除第一个8的时候就出错了

上述代码只能解决上述场景的问题(想用4替代3)

假设我们希望:左图中删除8,或者右图中删除3,以上代码都还是存在一定的逻辑漏洞。

3右数中的最小值就是右数的根,所以最后一句rightMinP->left=rightMin->_right就不正确;

并且rightMinP是空,所以会运行错误。

8的错误就是因为我们自己将rigthMinParent设置成了nullptr。

删除8的时候,cur指向的是8,但是rightMinParent指向的是10,10没有左节点,所以就不会进入while循环,rigthMinParent还是保持初始值nullptr

                       

最后发现,要删除最后一个数据13的时候又报错了,其原因是:

我们解决了删除有两个子节点的节点的父节点的空指针情况(赋值成cur解决了),但是没有考虑至多只有一个子节点的节点的父节点是空指针的情况。

                                       

这样的情况想要删除8,就会报错。

因为parent只有进入了查找的循环才会有值:

                             

如果根节点就是我们要删除的_root , 就会因为parent为nullptr而报错。

解决方案:单独判断即可。

这种情况只可能是:左子树为空并且cur就是_root

或者右子树为空并且cur就是_root , 所以此时parent一定是空。

if (cur->_left == nullptr) {
				if (parent == nullptr)
				{
					_root = cur->_right;
				}else 
				if (parent->_left==cur) {
					parent->_left = cur->_right;
				}else
				if (parent->_right==cur) {
					parent->_right = cur->_right;
				}
				delete cur;
				return true;
}
if (cur->_right == nullptr) {
				if (parent == nullptr)
				{
					_root = cur->_left;
				}else
				if (parent->_left == cur) {
					parent->_left = cur->_left;
				}else
				if (parent->_right == cur) {
					parent->_right = cur->_left;
				}
				delete cur;
				return true;
}

完整的删除代码:

bool Erase(const K& key) {
	Node* parent = nullptr;
	Node* cur = _root;
	//首先要去找到希望被删除的key
	while (cur) {
		if (cur->_key > key) {
			parent = cur;
			cur = cur->_left;
		}
		else if (cur->_key < key) {
			parent = cur;
			cur = cur->_right;
		}
		else {
			//此时找到了,准备进行删除
			//先处理最多只有一个孩子的节点
			if (cur->_left == nullptr) {
				if (parent == nullptr)
				{
					_root = cur->_right;
				}else 
				if (parent->_left==cur) {
					parent->_left = cur->_right;
				}else
				if (parent->_right==cur) {
					parent->_right = cur->_right;
				}
				delete cur;
				return true;
			}
			if (cur->_right == nullptr) {
				if (parent == nullptr)
				{
					_root = cur->_left;
				}else
				if (parent->_left == cur) {
					parent->_left = cur->_left;
				}else
				if (parent->_right == cur) {
					parent->_right = cur->_left;
				}
				delete cur;
				return true;
			}
			else {
				//删除有两个孩子的双节点
				//这次我们全部都用右树的最小节点
				Node* rightMin = cur->_right;
				Node* rightMinParent = cur;
				//先去找右节点最小的
				while (rightMin->_left) {
					rightMinParent = rightMin;
					rightMin = rightMin->_left;
				}
				//找到合适的替换值了就开始交换
				cur->_key = rightMin->_key;
				//希望被删除的位置一定是没有左节点的。
				if(rightMinParent->_left==rightMin)
				rightMinParent->_left = rightMin->_right;
				else
					rightMinParent->_right = rightMin->_right;

				delete rightMin;
				return true;
			}
		}
	}
	return false;
}

3. 搜索二叉树的实践运用

关于查找,目前为止我们有四种方法搜寻:

关于搜索树的使用:

场景1:在不在 key模型(set)

场景2:通过一个值找另外一个值 key/value模型 (map)

 

通过一个值找另一个值,在节点中存两个数据,可以通过一个找另外一个。

直接在原模版上直接改:

              

可以由此实现一个小字典:


至于cin>>str是如何被判断对错的:

string的流提取是被重载了的,返回值中的istream又去重载了内置类型bool

最后判断的其实是bool的真假。

 至于结束这样输入的方法:

1 . CTRL+C 但这样是杀进程,报错结束。

2 . CTRL+Z+换行 输入CTRL+Z+换行的时候让标志变为false , 退出循环


key-value的运用:

将Node节点中的数据设置成:string类型的水果名,和int类型的value用于计数

 

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

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

相关文章

人工智能对教育4.0的影响

随着技术变革的加速&#xff0c;迫切需要支持教育系统管理新的机遇和风险。如果管理得当&#xff0c;技术为帮助教育系统实现教育4.0提供了一个独特的机会。教育4.0是一种专注于为学习者提供适合未来的能力、技能、态度和价值观的教学方法。“教育4.0”是由全球教育专家、从业者…

艾体宝干货丨OIDA之二:掌握数据包分析-学会识别

在 OIDA 方法&#xff08;观察、识别、剖析、分析&#xff09;中&#xff0c;识别阶段对于在捕获的网络流量中精确定位相关数据至关重要。本文重点介绍如何在这一关键步骤中有效使用 Wireshark 和 Profitap 的 IOTA。 OIDA方法系列文章主要包含四个部分&#xff0c;分别是观察…

鸿蒙OS 应用基础知识

APP HarmonyOS 的应用软件包以 APP Pack&#xff08;Application Package&#xff09;形式发布&#xff0c;它是由一个或多个 HAP&#xff08;HarmonyOS Ability Package&#xff09;以及描述每个 HAP 属性的 pack.info 组成。HAP 是 [Ability]的部署包&#xff0c;HarmonyOS …

虚拟机安装VMware-tools详细教程

这里以VM16.12版本为例子&#xff0c;所有windows系统在所有虚拟机版本上都是一样的操作&#xff0c;参考即可 第一步打开虚拟机&#xff0c;这里需要注意的是虚拟机设备要有CD/DVD驱动器&#xff0c;这也是很多人说vmtool安装按钮是灰色的原因 第二步:打开虚拟机&#xff0c;…

linux入门到实操-1 Linux概述、诞生过程、发行版本,如何安装?

教程来源&#xff1a;B站视频BV1WY4y1H7d3 3天搞定Linux&#xff0c;1天搞定Shell&#xff0c;清华学神带你通关_哔哩哔哩_bilibili 整理汇总的课程内容笔记和课程资料&#xff0c;供大家学习交流下载&#xff1a;夸克网盘分享 本文内容为完整笔记的入门篇 概述部分历史内容…

git push失败原因上传的文件超过了Gitee的上限100M

! [remote rejected] master -> master (pre-receive hook declined) error: failed to push some refs to 这个错误信息表明你在尝试将更改推送到Gitee的socket_service仓库时遇到了问题。具体来说&#xff0c;问题出在你尝试推送的文件大小超过了Gitee平台设定的限制。Git…

Science Robotics 在小动物模型中实现渐进和可逆主动脉收缩的软机器人平台

前言速览&#xff1a;目前对左心室压力过载引起心脏重构过程的理解主要来源于主动脉束带的动物模型。然而&#xff0c;这些研究未能同时控制疾病的进展和逆转&#xff0c;阻碍了其临床意义。为此&#xff0c;来自哈佛大学、麻省理工学院等的研究人员介绍了一种基于植入式可扩张…

回馈式负载箱的操作和维护

回馈式负载箱是用于测试电源设备&#xff08;如发电机、逆变器等&#xff09;性能的设备&#xff0c;它可以模拟真实的负载情况&#xff0c;通过调节负载的大小和类型&#xff0c;来检测电源设备的输出能力和稳定性。回馈式负载箱的操作和维护对于保证其正常工作和延长使用寿命…

19:I2C一:程序模拟I2C通信时序

I2C 1、什么是I2C2、I2C的通信时序2.1&#xff1a;起始信号2.2&#xff1a;停止信号2.3&#xff1a;主机向从机发送一个字节数据2.4&#xff1a;主机向从机读取一个字节数据2.5&#xff1a;主机接收应答2.6&#xff1a;主机发送应答 3、程序模拟I2C的通信时序3.1&#xff1a;指…

为什么企业需要数据目录?

想象一下&#xff0c;如果在没有目录系统的庞大图书馆里寻找一本特定的书&#xff0c;你可能会耗费无数个小时搜索&#xff0c;但最终却一无所获。 同理&#xff0c;企业的数据如果没有一个组织良好、易于搜索的系统&#xff0c;也无法充分发挥其潜力。企业数据目录能够简化这一…

“爱满中华”与“民生之语”——全国人民的幸福之音!

近年来,随着科技的不断进步,数字化手段在各个领域的应用越来越广泛。在此背景下,我国宣传部推出了“爱满中华”全民自助补贴APP,旨在通过数字化手段,更有效地推行全民扶贫补助政策,而“爱满中华”自助补贴平台和“民生之语”利民通讯软件也正式被我国中信办称为国家网络未来工程…

组合总和IV(力扣---动态规划)

文章目录 1.题目描述2.解题思路3.代码实现 1.题目描述 题目描述见&#xff1a;组合总和IV 2.解题思路 视频参考&#xff1a; 组合总和IV 以上述的示例1为例&#xff1a; dp[4]的含义是什么&#xff1f;和为4的组合有多少种 一般动态规划是可以由前面的dp[3]推导而来的 dp[3…

使用Lua碰到的问题、踩坑记录

文章目录 1. 获得整除结果2. 在数组末尾添加元素的两种写法3. 接收可变参数 ...4. 复杂表结构的定义5. 一行代码, 同时定义多个变量6. 数组与集合的遍历方式7. 函数声明和调用的位置关系 1. 获得整除结果 用 “/” 来做除法, 得到的结果中总会带有小数. 可以使用 math.modf() …

windows下使用 vscode 远程X11服务GUI显示的三种方法

总结三种方法&#xff1a; 前言 ssh连接要使用-XY。 -X 表示ForwardX11&#xff0c;-Y 是ForwardX11Trusted&#xff0c;就是允许了X11转发&#xff0c;可以实现图形显示&#xff0c;虽然很多工具会默认使用这些参数&#xff0c;但是一般手动加也不会报错&#xff0c;所以就…

命令行中的引号

程序&#xff0c;或者说如果main函数中的参数带引号&#xff0c;那么带引号的部分会被当作一个单独的参数&#xff0c;并传递给main函数。并且解析后&#xff0c;引号会被去掉&#xff0c;并分别传递给argv中的各个参数。 如图所示&#xff0c;第3个参数中的引号被丢弃。

引领智能家居新风尚,WTN6040F门铃解决方案——让家的呼唤更动听

在追求高效与便捷的智能家居时代&#xff0c;每一个细节都承载着我们对美好生活的向往。WTN6040F&#xff0c;作为一款专为现代家庭设计的低成本、高性能门铃解决方案&#xff0c;正以其独特的魅力&#xff0c;悄然改变着我们的居家生活体验。 芯片功能特点&#xff1a; 1.2.4…

ubuntu使用wireshark抓取数据

工具 aircrack-ng工具&#xff1b;wireshark工具 sudo apt-get install aircrack-ng2 sudo add-apt-repository ppa:wireshark-dev/stable sudo apt update sudo apt install -y wireshark使用 airmon-ng 执行ifconfig查看网卡 设置网卡为监听模式&#xff1a;sudo airmo…

UNION嵌套STRUCT的两种类型

1. STRUCT里面的总长度大于UNION中的最大长度 在UNION类型中&#xff0c;嵌套如STRUCT类型&#xff0c;其中STRUCT的类型还比UNION类型中最大的类型的长度还长的时候&#xff0c;会如何处理呢&#xff0c;看下面示例 程序源码 #include "stdafx.h"typedef unsigned…

研究生考试报名上传手持身份证照片,如何拍清晰并且过审

研究生考试是中国高等教育中的一项重要考试&#xff0c;在每年9月下旬开始。在报名过程中&#xff0c;上传手持身份证照片是一个关键步骤&#xff0c;它直接关系到报名是否能够顺利通过审核。本文将为你提供4个实用的技巧&#xff0c;帮助你拍摄出既清晰又符合要求的手持身份证…

【三刷C语言】各种注意事项

这里是阿川的博客&#xff0c;祝您变得更强 ✨ 个人主页&#xff1a;在线OJ的阿川 &#x1f496;文章专栏&#xff1a;C语言入门到进阶 &#x1f30f;代码仓库&#xff1a; 写在开头 现在您看到的是我的结论或想法&#xff0c;但在这背后凝结了大量的思考、经验和讨论 目录 1.…