【数据结构】二叉搜索树——高阶数据结构的敲门砖

目录

树概述

二叉搜索树概述

概念

特性

元素操作

插入

删除

模拟实现

框架

查找

插入

删除


树概述

——在计算机中是一种很常见的数据结构

树是一种很强大的数据结构,数据库,linux操作系统管理和windows操作系统管理所有文件的结构就是一颗树形结构

每个树有且只有一个根节点——根节点个数只有一个,节点可存储数据。

根节点往下可以有多个叶子节点,每个叶子节点也可以有多个叶子节点

如下图

每一个节点都可以是父亲节点,每个父亲节点的叶子节点都是孩子节点

根节点的深度为0,最后一层节点的深度为lgN

如果每一层深度的孩子节点的个数小于等于2,便是二叉树

左边的节点称为左孩子节点,右边的节点称为右孩子节点

如下图

如何正确的看一颗二叉树呢?

3是根节点,6作为3的左孩子也可以是一颗子树,这颗树是以6为根节点的树,也是3的左子树。以22为根节点来作为3的右子树也是一样的。

最小的子树是左孩子和右孩子都为空的叶子节点

也就是说每一个节点都可以作为根节点,从而成为父亲节点的子树,子树又可以拆成子树,直到左右都为空的叶子节点为止(叶子节点也可以看作一颗子树)。

遍历一颗树应该用递归的思维,如下是遍历顺序

二叉树前序遍历:根 ——> 左子树——>右子树

二叉树中序遍历:左子树——>根——>右子树

二叉树后序遍历:左子树——>右子树——>根

二叉树层序遍历:一条龙遍历,可参考【C++】详解STL的适配器容器之一:优先级队列 priority_queue-CSDN博客

二叉搜索树概述

概念

二叉搜索树是一种二叉树结构,二叉搜索树存储数据时需要符合如下规则(搜索树是支持泛型的,为了方便理解,小编以int类型为例):

左孩子节点的值 < 父亲节点的值 < 右孩子节点的值

如下示例:

特性

对于所有数据而言,

从整棵树的根节点开始,不断地找左子树,没有左孩子的左子树存的值最小

从整棵树的根节点开始,不断地找右子树,没有右孩子的右子树存的值最大

如果整棵树走中序遍历则是升序

那么用搜索二叉树找一个节点的效率如何呢?

查找的规则很简单,比根节点的值大往右子树走,比根节点的值小往左子树走。每查找一次就能“砍”掉一半数据,只需要查找log N次。类似于二分查找。

上述查找条件是:整棵树基本符合二叉树结构。如果是比较极端的结构,效率会接近N^2。比如下述结构

二叉搜索树搜索的效率的上限很高,下限很低

元素操作

插入

二叉搜索树中不允许有相等的值存在,因为要维持 “左树的值小于根小于右数的值” 这一特性。

如果要插入的值在树中不存在,这个值会插入到中间某个位置吗?

答案是不会,因为这个值一定会从整棵树的根节点比到叶子节点,然后插入到叶子节点的后面。如下示意图:

删除

删除的情况比较复杂,大致可以分为两种情况

孩子节点的个数是1或0

假设被删节点为cur,被删节点的父亲节点为fatherNode

先找是否有cur,找不到,删除失败。

找到了,判断curfatherNode的左孩子还是右孩子

是左孩子,让fatherNode的左指针指向cur的孩子节点

是右孩子,让fatherNode的右指针指向cur的孩子节点

如果没有孩子节点,则fatherNode的指针指向空

改变指向关系后整棵树就没有cur节点了,最后释放cur的内存

fatherNode的左孩子被删应该有新的左孩子接替,右孩子应该有新的右孩子接替,这样才不会破坏二叉搜索树的结构,所以要判断curfatherNode的左孩子还是右孩子

孩子节点的个数是2

孩子节点是1或0的时候只需改变指向关系,然后再释放被删节点的内存即可。

那么被删节点的孩子个数是两个的时候还能这么做吗?

显然是不能的,因为一个父亲节点最多只能有两个孩子节点

删除孩子节点的个数是2的节点,最核心的思想是:

被删节点为cur但不直接删cur这个节点,因为这样会破坏这颗树的结构。

一个孩子节点为1或0的节点为leftMax_Node,让leftMax_Node节点的值和cur节点的交换,然后删leftMax_Node节点

通过替换的方法就完成了cur节点的删除

那么怎么找孩子节点为1或0的节点呢?

左子树的最大节点右子树的最小节点

注意:是被删节点cur的左树(右树),而不是整棵树的左树(右树)

在上文二叉树的特性中已经提过,找最大值或最小值,找的一定是叶子节点

整个二叉搜索树中,左子树存小值,右子树存大值。

如果选左子树的最大值放在根节点的位置,依旧符合左子树的值小于根小于右子树这一规则,删掉该节点不会破坏二叉树的结构。

左子树的最大节点或右子树的最小节点是完美的  “替罪羊” 

模拟实现

模板相关知识可参考:

【C++】详解C++的模板-CSDN博客

内存管理相关知识可参考:

【C++】C++的内存管理-CSDN博客

框架

节点的设计

有两个指针指向左右孩子

在用一个变量存值

template <class K>
class BSNode
{
public:

	BSNode(const K& Key = K())
		:_left(nullptr)
		,_right(nullptr) 
		,_key(Key)  
	{
	}

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

};

二叉搜索树的设计

只需要一个节点指针指向整棵树的根节点即可

template <class K>
class BSTree
{
	typedef BSNode<K> node;

//......

private:

	node* _root;  
};

查找

查找的代码很简单,小编就不细讲了。

有一点要提醒大家,大家一定要用判断语句把查找条件分开,比根大就往右树查找,比根小就往左树查找。千万不要写成暴力查找——不管值的大小,遍历整棵树

代码如下

bool _FindNode(node* root, const K& key) 
{
	while (root)
	{
		if (root->_key < key) 
		{
			root = root->_right;
		}
		else if (root->_key > key)
		{
			root = root->_left;
		}
		else
		{
			return false;
		}

		return true;
	}
}

可以把上述代码设计成私有,再封装一层共有的函数,这样做的意义是不用传节点的指针了

bool FindNode(const K& key)
{
	return _FindNode(_root, key);           
}

插入

接口

bool Insert(const K& key) 

首先应该判断整棵树是否为空,如果为空,直接让_root直接指向新节点

if (_root == nullptr) //如果为空
{
	_root = new node(key); //头节点指针指向新节点
	return true;
}

二叉搜索树是不允许有重复的值出现的

应该再写一遍查找的逻辑,因为_root是指向整棵树的根的,所以需要定义一个临时指针,当我们找到相等的值时,插入失败。

node* cur = _root;  
node* fatherNode = nullptr; 

while (cur) 
{
	if (cur->_key < key) //插入的元素比当前节点的值大,
	{
		fatherNode = cur; //记录父亲节点的指针
		cur = cur->_right;   //往右树找
	}
	else if (cur->_key > key)
	{
		fatherNode = cur;
		cur = cur->_left;
	}
	else
	{
		return false;
	}
}

没找到的话,说明临时指针已经找到空了,临时指针的父亲节点就是合适的叶子节点了,插在叶子节点的孩子位置即可,是左孩子还是右孩子需要和父亲节点比较大小

  node* newNode =  new node(key);
  if (fatherNode->_key < key)//判断要插入父亲节点的左边还是右边
  {
	  fatherNode->_right = newNode; 
  }
  else
  {
	  fatherNode->_left = newNode; 
  }

删除

接口

bool Erase(const K& key)    

先判断是否是空树

if (nullptr == _root)//如果是空树,删除失败
{
	return false;
}

和插入类似,要判断被删的节点是否存在

node* cur = _root; //临时变量,cur是指要删除的节点
node* fatherNode = nullptr; //cur的父亲节点,

while (cur)//找要删除的节点
{
	if (cur->_key < key) 
	{
		fatherNode = cur;
	cur = cur->_right;
	}
	else if (cur->_key > key)
	{
		fatherNode = cur;
		cur = cur->_left;
	}
	else
	{
		break;
	}
}
if (nullptr == cur) //没找到要删除的节点,删除失败
{
	return false;
}

下面就是要删除节点的逻辑了,上文已经讲了删除的原理,这里细讲代码细节

孩子节点数量为1或0

节点的孩子树不管是1还是0,可以只写左孩子为空的情况和右孩子为空的情况,左右孩子都为空的情况可以不写。

因为左右孩子都为空的情况,可以走左孩子为空的逻辑,只是指向新节点还是指向空的问题。

	if (cur->_left == nullptr) //要删除的节点左为空
	{
		if (cur == _root)  //极端情况,
		{
			_root = cur->_right;
			delete cur;
			return true;
		}

		if (fatherNode->_left == cur)  //判断要删除的节点在父亲节点的左边还是右边
			fatherNode->_left = cur->_right;
		else                                   //改变指向
			fatherNode->_right = cur->_right;

		delete cur; //删除
		return true;

	}
	else if (cur->_right == nullptr)//要删除的节点右为空
	{
		if (cur == _root) //极端情况
		{
			_root = cur->_left;  
			delete cur; 
			return true;
		}

		if (fatherNode->_left == cur)  //判断要删除的节点在父亲节点的左边还是右边
			fatherNode->_left = cur->_left; 
		else                               //改变指向
			fatherNode->_right = cur->_left; 

		delete cur; //删除节点
		return true;
	}

极端情况做了特殊处理

极端情况如下图所示

孩子节点数量为2

else //要删除的节点左右都不为空
{
	node* leftMax_Node = cur->_left;  //左树最大节点 (被删节点的左树)
	node* leftMax_FatherNode = cur; //左树最大节点的父亲节点             

	while (leftMax_Node->_right)      //找代替节点,左树的最右节点(找左树的最大值)
	{
		leftMax_FatherNode = leftMax_Node; 

		leftMax_Node = leftMax_Node->_right;     
	}

	std::swap(cur->_key, leftMax_Node->_key);   //交换完之后,左树的最大节点就是要删的值了  

	if (leftMax_FatherNode->_left == leftMax_Node)  //要判断被删节点是父亲节点的左孩子还是右孩子
		leftMax_FatherNode->_left = leftMax_Node->_left;
	else
		leftMax_FatherNode->_right = leftMax_Node->_left;

	delete leftMax_Node;    
	return true;
}

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

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

相关文章

PS教程08

光效 新建一个图层 按住Altdelete将颜色填充完毕。 转换智能对象 作用&#xff1a;我们可以对这个图层无限期修改 接下来找到滤镜-渲染-镜头光晕 选择需要的亮度和镜头类型&#xff0c;点击确定 如果发现位置不太理想&#xff0c;可以直接双击右下角的镜头光晕&#xff0c;…

swagger-ui页面接口的入参出参与代码实体类不一致有差异、swagger请求实体与预期定义不符、swagger参数与实体中参数不一致原因分析

文章目录 一、问题背景二、问题原因及解决方法 一、问题背景 项目集成swagger之后&#xff0c;发现有个接口的请求跟接口对应不上&#xff0c;把其他接口的请求参数放到当前这个请求上了。 如下图&#xff1a;test1接口的请求参数是其他请求的&#xff0c;并不是test1接口的 …

RFID芯片掼蛋牌:高科技与传统玩法结合,游戏体验焕然一新。

火爆“出圈”的掼蛋&#xff0c;是一种玩法相当鲜明的智力游戏。近年来得到了不少的推广和发展&#xff0c;各地举办了各种掼蛋比赛和活动&#xff0c;吸引了大量的参赛者和观众。此外&#xff0c;一些企业和机构也开始将掼蛋作为一种企业文化或者社交活动的方式&#xff0c;通…

无需开孔,安全美观:低功耗微波雷达模块开启宠物喂食器新未来

在快节奏的现代生活中&#xff0c;宠物已成为许多家庭的重要成员。然而&#xff0c;忙碌的主人常常为如何确保宠物按时进食而困扰。近年来&#xff0c;智能家居技术飞速发展&#xff0c;宠物喂食器也逐渐智能化&#xff0c;极大地方便了宠物主人。今天&#xff0c;我们要介绍的…

项目成功的关键要素:进度管理

在项目管理中&#xff0c;进度管理是决定项目成败的关键因素之一。它关乎项目能否在预定的时间范围内高效、准确地完成既定目标。 一、进度管理的重要性 1、时间控制&#xff1a;项目的成功往往与时间的把握息息相关。进度管理能够确保项目在既定的时间框架内有序进行&#x…

MySQL select for update 加锁

背景 当多人操作同一个客户下账号的时候&#xff0c;希望顺序执行&#xff0c;某个时刻只有一个人在操作&#xff1b;当然可以通过引入redis这种中间件实现&#xff0c;但考虑到并发不会很多&#xff0c;所以不想再引入别的中间件。 表结构 create table jiankunking_accoun…

肆拾玖坊FFC模式,社群裂变,股权众筹设计

肆拾玖坊商业模式&#xff0c;白酒新零售体系&#xff0c;众筹合伙模式 坐标&#xff1a;厦门&#xff0c;我是易创客肖琳 深耕社交新零售行业10年&#xff0c;主要提供新零售系统工具及顶层商业模式设计、全案策划运营陪跑等。 联想高管辞职跑去卖酒&#xff0c;6年狂赚30亿&…

手机“本地”也能玩转AI大模型 - 万物皆可AI

友友们&#xff0c;大家好&#xff01;我最近发现一个很有意思的AI项目——MiniCPM-V&#xff0c;可以说它将AI技术的应用推向了一个全新的高度&#xff0c;让我们能够将GPT-4V级的多模态大模型直接部署在我们的手机上&#xff0c;而且完全不需要联网&#xff0c;真正的手机本地…

Unity版本使用情况统计(更新至2024年4月)

UWA发布&#xff5c;本期UWA发布的内容是第十四期Unity版本使用统计&#xff0c;统计周期为2023年11月至2024年4月&#xff0c;数据来源于UWA网站&#xff08;www.uwa4d.com&#xff09;性能诊断提测的项目。希望给Unity开发者提供相关的行业趋势作为参考。 2023年11月 - 2024年…

C++候捷stl-视频笔记1

认识headers、版本、重要资源 STL的核心思想是泛型编程 新式头文件内的组件封装在命名空间std中&#xff1a; using namespace std; using std::cout;或std::vector vec; 旧式头文件内的组件不封装在命名空间std中 注:不建直接使用using namespace xxx&#xff0c;如果使用的…

apexcharts数据可视化之极坐标区域图

apexcharts数据可视化之极坐标区域图 有完整配套的Python后端代码。 本教程主要会介绍如下图形绘制方式&#xff1a; 基础极坐标区域图单色极坐标区域图 基础极坐标区域图 import ApexChart from react-apexcharts;export function BasicPolar() {// 数据序列const series…

深入解析多维数组与主对角线元素之和

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、引言&#xff1a;多维数组的奥秘 二、多维数组的基本概念 1. 定义与创建 2. 维度与形…

Linux服务器安装docker,基于Linux(openEuler、CentOS8)

本实验环境为openEuler系统(以server方式安装)&#xff08;CentOS8基本一致&#xff0c;可参考本文) 目录 知识点实验 知识点 Docker 是一个开源的应用容器引擎。它允许开发者将应用及其所有依赖项打包到一个可移植的容器中&#xff0c;并发布到任何支持Docker的流行Linux或Wi…

歌曲转换成mp3格式超简单!快来试试看

在数字音乐时代&#xff0c;我们经常从各种来源下载或收藏到各种音频文件&#xff0c;但有时这些文件可能并不是我们设备所支持的常见格式&#xff0c;尤其是当我们更倾向于使用MP3格式的时候。因此&#xff0c;对于那些希望统一音乐库格式的人来说&#xff0c;将歌曲转换成mp3…

redis面试知识点

Redis知识点 Redis的RDB和AOF机制各是什么&#xff1f;它们有什么区别&#xff1f; 答&#xff1a;Redis提供了RDB和AOF两种数据持久化机制&#xff0c;适用于不同的场景。 RDB是通过在特定的时刻对内存中的完整的数据复制快照进行持久化的。 RDB工作原理&#xff1a; 当执行…

深入理解深度学习中的激活层:Sigmoid和Softmax作为非终结层的应用

深入理解深度学习中的激活层&#xff1a;Sigmoid和Softmax作为非终结层的应用Sigmoid 和 Softmax 激活函数简介Sigmoid函数Softmax函数 Sigmoid 和 Softmax 作为非终结层多任务学习特征变换增加网络的非线性实际案例 注意事项结论 深入理解深度学习中的激活层&#xff1a;Sigmo…

探索研究大语言在生物识别技术——使用ChatGP-4从完成从人脸识别到年龄估计

0.引言 论文提出以下几要点&#xff1a; &#xff08;1&#xff09;. 人脸识别、性别检测和年龄估计的性能评估&#xff1a; 进行了一项研究&#xff0c;使用GPT-4这样的大型语言模型来处理人脸识别、性别检测和年龄估计等任务。这些任务是生物识别技术中的常见应用&#xff…

【评测体验】OrangePi AIpro 系统构建及性能测试

感谢香橙派社区能够邀请我评测这款开发板&#xff0c;祝愿国产开发板发展越来越好&#xff01;在这里能够尽自己的一份力量是我的荣幸。 这篇文章是 OrangePi AIpro 开发板的评测&#xff0c;内容包括开发板简介、系统构建过程、系统性能测试、压缩算法性能测试、内核编译。 到…

分析和设计算法

目录 前言 循环不变式 n位二进制整数相加问题 RAM模型 使用RAM模型分析 代码的最坏情况和平均情况分析 插入排序最坏情况分析 插入排序平均情况分析 设计算法 分治法 总结 前言 循环迭代&#xff0c;分析算法和设计算法作为算法中的三个重要的角色&#xff0c;下面…

【深度 Q 学习-01】 Q学习概念和python实现

文章目录 一、说明二、深度 Q 学习概念三、python实现四、结论 关键词&#xff1a;Deep Q-Networks 一、说明 在强化学习 &#xff08;RL&#xff09; 中&#xff0c;Q 学习是一种基础算法&#xff0c;它通过学习策略来最大化累积奖励&#xff0c;从而帮助智能体导航其环境。它…