【数据结构】超详细一文带小白轻松全面理解 [ 二叉搜索树 ]—— [从零实现&逐过程分析&代码演示简练易懂]

news2024/12/24 2:15:52

前言

大家好吖,欢迎来到 YY 滴数据结构系列 ,热烈欢迎! 本章主要内容面向接触过C++的老铁
主要内容含:
在这里插入图片描述

欢迎订阅 YY滴数据结构专栏!更多干货持续更新!以下是传送门!

目录

  • 一.二叉搜索树的基本概念
  • 二.增删查改基本操作
    • 1.二叉搜索树的查找(分析&代码演示)
      • 分析
      • 代码演示
    • 2.二叉搜索树的插入(分析&代码演示)
      • 分析
      • 代码演示
    • 3.二叉搜索树的删除【※核心重点】(分析&代码演示)
      • 分析
      • 代码演示
    • 4.二叉搜索树的中序遍历(分析&代码演示)
      • 分析
      • 代码演示
  • 三.二叉搜索树的性能问题:需要AVL树...红黑树...
  • 四.二叉搜索树的完整实现代码演示
  • 五.进阶二叉树习题传送门

一.二叉搜索树的基本概念

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

  1. 若它的左子树不为空,则 左子树 上所有节点的值都 小于 根节点的值
  2. 若它的右子树不为空,则 右子树 上所有节点的值都 大于 根节点的值
  3. 它的 左右子树 也分别为二叉搜索树 ;
    在这里插入图片描述

二.增删查改基本操作

在这里插入图片描述

//结点模板
template<class K>
struct BSTreeNode
{
	BSTreeNode<K>* _left;
	BSTreeNode<K>* _right;
	K _key;

	BSTreeNode(const K& key)
		:_left(nullptr)
		,_right(nullptr)
		,_key(key)
	{}
};
//在二叉搜索树模板中
typedef BSTreeNode<K> Node;

1.二叉搜索树的查找(分析&代码演示)

分析

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

代码演示

//查找操作
	bool Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key < key)//从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找 
			{
				cur = cur->_right;
			}
			else if (cur->_key > key)
			{
				cur = cur->_left;
			}
			else
			{
				return true;
			}
		}

		return false;//最多查找高度次 ,走到到空,还没找到,这个值不存在。
	}

2.二叉搜索树的插入(分析&代码演示)

分析

  • 树为空,则直接新增节点,赋值给root指针
  • 树不空, 按二叉搜索树性质的查找方式(前后指针) 找到插入位置,插入新节点

代码演示

//插入操作
	bool Insert(const K& key)
	{
	//树为空,则直接新增节点,赋值给root指针
		if (_root == nullptr)
		{
			_root = new Node(key);
			return true;
		}
		
	//树不空, 按二叉搜索树性质的查找方式(前后指针) 找到插入位置,插入新节点
		Node* parent = nullptr;//后指针
		Node* cur = _root;//前指针
		while (cur)
		{
			if (cur->_key < key)//比keycur的_key大,往右走
			{
				parent = cur;
				cur = cur->_right;
			}
			else if(cur->_key > key)//比keycur的_key小,往左走
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}
//cur走到空了,开始给插入的key值创建结点,根据其比后一个结点(parent)大还是小,决定其是插在左还是右
		cur = new Node(key); 
		if (parent->_key < key)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}

		return true;
	}

3.二叉搜索树的删除【※核心重点】(分析&代码演示)

分析

  • 首先查找元素是否在二叉搜索树中,如果不存在,则返回
  • 否则要删除的结点可能分下面四种情况:
  1. 要删除的结点无孩子结点
  2. 要删除的结点只有左孩子结点
  3. 要删除的结点只有右孩子结点
  4. 要删除的结点有左、右孩子结点
    在这里插入图片描述
  • 对上面四种情况整理后(1与2,3分别结合),剩下下面2种情况(直接删除,替换法),分出3种具体情况(直接删除占两种):
    在这里插入图片描述
  • 直接删除情况: 只有左/右/无孩子结点(无孩子,只有一个孩子)
    (双亲结点指向被删除节点的左还是右————取决于被删除节点是其双亲节点的左还是右节点)
  • 情况1:被删除节点是其双亲节点的左节点,删除该结点且使被删除节点的双亲结点指向被删除节点的 左孩子 结点
  • 情况2:被删除节点是其双亲节点的右节点,删除该结点且使被删除节点的双亲结点指向被删除结点的 右孩子 结点
    在这里插入图片描述
  • 还要考虑结点为根结点情况:
    在这里插入图片描述
  • 替换法情况:【※核心难点】 (有两个孩子)
  • 情况3 :在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中,再来处理该结点的删除问题
  • 分析:要 找到左子树的最大(右)结点,或者 右子树的最小(左)结点(下图演示中是找到左子树的最大结点)
  • 具体过程分析:
  1. 设置前后指针,留一个cur指针指向要删除结点,parent指针跟着LeftMax指针向下逐个移动
  2. 找到leftMax以后,交换其和cur的数值,(收完尾后,最后一步再将指针也一同转移)
  3. 要分为两种情况(如下图所示) (1) leftMax指针的左指针为空,(2) leftMax指针的左指针不为空
    (为什么不用讨论右指针呢?因为leftMax的右指针必定为空,否则leftMax会继续向下移动)
  4. 因为采用的是前后指针法,所以这时留下的后指针(parent)就对应指向leftMax的左/右结点
  5. 最后将cur指针指向leftMax,leftMax动不动无所谓
    在这里插入图片描述

代码演示

//删除操作
	bool Erase(const K& key)
	{
		Node* parent = nullptr;//后指针
		Node* cur = _root;//前指针

		while (cur)
		{
//通过二叉搜索树规则向下查找
			if (cur->_key < key)      
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
//直接删除情况:只有左/右/无孩子结点
//(双亲结点指向被删除节点的左还是右————取决于被删除节点是其双亲节点的左还是右节点) 
			else // 找到了
			{
				 // 左为空      
				if (cur->_left == nullptr)
				{
					if (cur == _root)
					{
						_root = cur->_right;
					}
					else
					{
						if (parent->_right == cur)//被删除节点是其双亲节点的右节点   
						{
							parent->_right = cur->_right;//删除该结点且使被删除节点的双亲结点指向被删除结点的 右孩子 结点
						}
						else//被删除节点是其双亲节点的左节点  
						{
							parent->_left = cur->_right;//删除该结点且使被删除节点的双亲结点指向被删除结点的 左孩子 结点
						}
					}
				}
				// 右为空    
				else if (cur->_right == nullptr)
				{
					if (cur == _root)
					{
						_root = cur->_left;
					}
					else
					{
						if (parent->_right == cur)
						{
							parent->_right = cur->_left;
						}
						else
						{
							parent->_left = cur->_left;
						}
					}					
				} 

// 替换法情况:左右都不为空 
				else
				{
					// 找替代节点
					Node* parent = cur;
					Node* leftMax = cur->_left;
					while (leftMax->_right)
					{
						parent = leftMax;
						leftMax = leftMax->_right;
					}

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

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

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

		return false;
	}

4.二叉搜索树的中序遍历(分析&代码演示)

分析

  • 中序遍历要从通过模板实例化的树中调用中序遍历函数
  • 需要传根结点指针,但是 根结点指针是在private域中,域外不能直接传一个根结点指针 ,所以要引入_InOrder函数,在二叉搜索树模板中 再次封装一层

代码演示

void TestBSTree1()
{
	int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	BSTree<int> t;
	for (auto e : a)
	{
		t.Insert(e);
	}

	t.InOrder();  //需要传根结点指针,但是根结点指针是在private域中,域外不能直接传一个根结点指针,
	              //所以要引入_InOrder函数,在二叉搜索树模板中再次封装一层
}
//中序遍历——————————————————————————————————————————为了解决中序要传入根节点的问题,引入_InOrder函数
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	void _InOrder(Node* root)
	{
		if (root == NULL)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);
	}

三.二叉搜索树的性能问题:需要AVL树…红黑树…

  • 插入和删除操作都必须先 查找,查找效率代表了二叉搜索树中各个操作的性能
  • 当二叉搜索树 退化为单支时,其效率为O(N),二叉搜索树的性能就失去了
  • 对二叉搜索树进行改进后,得到的AVL树红黑树效率为 Log(N)

四.二叉搜索树的完整实现代码演示

//结点模板
template<class K>
struct BSTreeNode
{
	BSTreeNode<K>* _left;
	BSTreeNode<K>* _right;
	K _key;

	BSTreeNode(const K& key)
		:_left(nullptr)
		,_right(nullptr)
		,_key(key)
	{}
};

//二叉搜索树类模板
template<class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
public:

//初始化列表
	BSTree()
		:_root(nullptr)
	{}
	

//查找操作
	bool Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key < key)
			{
				cur = cur->_right;
			}
			else if (cur->_key > key)
			{
				cur = cur->_left;
			}
			else
			{
				return true;
			}
		}

		return false;
	}

//插入操作
	bool Insert(const K& key)
	{
		if (_root == nullptr)
		{
			_root = new Node(key);
			return true;
		}

		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if(cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}

		cur = new Node(key);
		if (parent->_key < key)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}

		return true;
	}


//删除操作
	bool Erase(const K& key)
	{
		Node* parent = nullptr;//后指针
		Node* cur = _root;//前指针

		while (cur)
		{
//通过二叉搜索树规则向下查找
			if (cur->_key < key)      
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
//直接删除情况:只有左/右/无孩子结点
//(双亲结点指向被删除节点的左还是右————取决于被删除节点是其双亲节点的左还是右节点) 
			else // 找到了
			{
				 // 左为空      
				if (cur->_left == nullptr)
				{
					if (cur == _root)
					{
						_root = cur->_right;
					}
					else
					{
						if (parent->_right == cur)//被删除节点是其双亲节点的右节点   
						{
							parent->_right = cur->_right;//删除该结点且使被删除节点的双亲结点指向被删除结点的 右孩子 结点
						}
						else//被删除节点是其双亲节点的左节点  
						{
							parent->_left = cur->_right;//删除该结点且使被删除节点的双亲结点指向被删除结点的 左孩子 结点
						}
					}
				}
				// 右为空    
				else if (cur->_right == nullptr)
				{
					if (cur == _root)
					{
						_root = cur->_left;
					}
					else
					{
						if (parent->_right == cur)
						{
							parent->_right = cur->_left;
						}
						else
						{
							parent->_left = cur->_left;
						}
					}					
				} 

// 替换法情况:左右都不为空 
				else
				{
					// 找替代节点
					Node* parent = cur;
					Node* leftMax = cur->_left;
					while (leftMax->_right)
					{
						parent = leftMax;
						leftMax = leftMax->_right;
					}

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

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

					cur = leftMax;
				}

				delete cur;
				return true;
			}
		}

		return false;
	}


//中序遍历——————————————————————————————————————————为了解决中序要传入根节点的问题,引入_InOrder函数
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	void _InOrder(Node* root)
	{
		if (root == NULL)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);
	}


private:
	Node* _root;
};


void TestBSTree1()
{
	int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	BSTree<int> t;
	for (auto e : a)
	{
		t.Insert(e);
	}

	t.InOrder();

	t.Erase(4);
	t.InOrder();

	t.Erase(6);
	t.InOrder();

	t.Erase(7);
	t.InOrder();

	t.Erase(3);
	t.InOrder();

	for (auto e : a)
	{
		t.Erase(e);
	}
	t.InOrder();
}

五.进阶二叉树习题传送门

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

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

相关文章

【unity插件】Shader实现UGUI的特效——UIEffect为 Unity UI 提供视觉效果组件

文章目录 前言地址描述Demo 演示Installation 安装如何玩演示用法使用示例完结 前言 一般的shader无法直接使用在UI上&#xff0c;需要在shader中定义特定的面板参数&#xff0c;今天就来推荐github上大佬做的一套开源的一系列UGUI&#xff0c;Shader实现的特效——UIEffect 为…

微信小程序漏洞之accesskey泄露

更新时间&#xff1a;2023年09月08日09:42:52 1. Accesskey泄露漏洞 这篇文章里面都是以我个人的视角来进行的&#xff0c;因为一些原因&#xff0c;中间删了一些东西&#xff0c;肯定有很多不正确的地方&#xff0c;希望大家能理解&#xff0c;也能指正其中的错误。 在以前…

【嵌入式开发学习】__搞了多年嵌入式,才发现全局变量是这样初始化的

最近&#xff0c;有个好学的小伙子突然问了我一个问题&#xff1a; 全局变量的初始值&#xff0c;是在哪里赋值的&#xff1f; 这个问题虽然说不是很重要&#xff0c;但是我很好奇。 为了给讲清楚这个原理过程&#xff0c;我专门建立一个基于 Renesas RH850 的简单工程&…

【vue】 实现 自定义 Calendar 日历

图例&#xff1a;自定义日历 一、标签自定义处理 <div class"date-box"><el-calendar v-model"state.currDate" ref"calendar"><template #header"{ date }"><div class"date-head flex"><div …

网页布局 flex

弹性盒模型 弹性盒模型&#xff08;Flexible Box模型&#xff09;&#xff0c;也被成为flexbox&#xff0c;是一种一维的布局模型&#xff08;也就是说一次只能处理一个维度上的元素布局&#xff0c;一行或者一列&#xff09;。它给flexbox的子元素之间提供了强大的空间分布和对…

CRM系统:助力数据服务企业,打造核心竞争力

近年来&#xff0c;数据服务企业开始走入大众视野。作为企业管理应用热门选手——CRM客户管理系统&#xff0c;可以助力企业实时数据应用先行者&#xff0c;提升业务转化与协同效率&#xff0c;进一步打造核心竞争力。下面我们说说&#xff0c;CRM系统对数据服务企业的作用。 …

运动耳机哪种类型好?2023年热门运动蓝牙耳机推荐

​在现代社会&#xff0c;耳机已经成为了人们生活中的必备数码设备。无论是在工作、学习还是娱乐中&#xff0c;我们都需要用到耳机。而在运动的时候&#xff0c;佩戴耳机更是成为了很多人的标配。那么&#xff0c;什么样的运动耳机最适合我们呢&#xff1f;下面&#xff0c;我…

【iOS】将网络请求封装在一个单例类Manager中(AFNetworking、JSONModel)

项目开发中会请求大量不同的API&#xff0c;若将网络请求三板斧直接写在Controller中会代码十分冗杂&#xff0c;干脆直接将AFNetWorking和JSONModel封装到一个全局的Manager单例类中&#xff0c;在Manager类中进行网络请求和数据解析 导入AFNetworking和JSONModel 参考【iOS…

SM5203 是一款完整的采用恒定电流/恒定电压的单节锂电池线性充电器

SM5203 1.2A/18V 锂电池线性充电芯片 简介&#xff1a; SM5203 是一款完整的采用恒定电流/恒定电压的单节锂电池线性充电器&#xff0c;并带有锂电池正负极反接保护功能&#xff0c;可以保护芯片和用户安全。由于采用了内部 PMOSFET 架构&#xff0c;加上防倒充电路&#xff…

opencv车牌识别<一>

目录 一、概述 二、ANPR简介 一、概述 本文将介绍创建自动车牌识别(Automatic Number Plate Recognition&#xff0c;ANPR)所需的步骤。对于不同的情形&#xff0c;实现自动车牌识别会用不同的方法和技术&#xff0c;例如&#xff0c;IR 摄像机、固定汽车位置、光照条件等…

hadoop 大数据环境配置 ssh免密登录 centos配置免密登录 hadoop(四)

1. 找到.ssh文件夹 cd ~ # 在.ssh文件夹下生成 # cd .ssh 2. 生成私钥公钥命令&#xff1a; ssh-keygen -t rsa3. 发送到需要免密机器&#xff1a; # hadoop23 是我做了配置。在host配置得机器ip和名称得映射 ssh-copy-id hadoop23 4. 成功

长假想要获得理想投放效果?巨量千川给出解决方案

巨量千川一直对商家的体验格外关注&#xff0c;了解到许多千川投手和商家在长假投放存在困难时&#xff0c;便深入了解原因&#xff0c;并针对问题提出了可行的解决方案。 发现原因有三&#xff1a; 其一&#xff0c;每逢节假日&#xff0c;大家都明白流量都会相对充足&#xf…

【科研新手指南4】ChatGPT的prompt技巧 心得

ChatGPT的prompt心得 写在最前面chatgpt咒语1&#xff08;感觉最好用的竟然是这个&#xff0c;简单方便快捷&#xff0c;不需要多轮对话&#xff09;chatgpt思维链2&#xff08;复杂任务更适用&#xff0c;简单任务把他弄复杂了&#xff09;机理chatgpt完整咒语1&#xff08;感…

5天飞驰1000公里的狗狗,救了整个镇的孩子

它&#xff0c;哈士奇&#xff0c;是个名副其实的网红&#xff0c;因其性格温顺、行为幼稚&#xff0c;被叫做“二哈”&#xff0c;成为现在很多搞笑视频、表情包的主角。 当我们调侃二哈可爱、蠢萌的时候&#xff0c;可能还没意识到&#xff0c;它的先祖们&#xff0c;竟是救命…

npm封装插件打包上传后图片资源错误

问题&#xff1a; npm封装插件&#xff1a;封装的组件页面涉及使用图片资源&#xff0c;在封装的项目里调用图片显示正常&#xff1b;但是打包上传后&#xff0c;其他项目引入使用报错找不到图片资源&#xff1b;图片路径也不对 获取图片的base64方法 解决方案&#xff1a; 将…

移远EC600U-CN开发板 11.14

控件探索-仪表&#xff08;lv.meter&#xff09; 1. 显示一个简单的仪表盘 def set_value(indic, v):meter.set_indicator_value(indic, v)# # A simple meter # meter lv.meter(scr) meter.center() meter.set_size(200, 200)# Add a scale first scale meter.add_scale()…

【C++代码】罗马数字和阿拉伯数字互转,双指针完成盛最多水的容器,自动机实现字符串转换整数

题目&#xff1a;整数反转 给你一个 32 位的有符号整数 x &#xff0c;返回将 x 中的数字部分反转后的结果。如果反转后整数超过 32 位的有符号整数的范围 $[−2^{31}, 2^{31} − 1] $&#xff0c;就返回 0。 记 rev 为翻转后的数字&#xff0c;为完成翻转&#xff0c;我们可以…

WebSocket Day04 : 消息推送

前言 随着Web应用程序的不断发展&#xff0c;实时性和交互性成为了用户体验中至关重要的一部分。传统的HTTP协议在处理实时数据传输方面存在一些局限性&#xff0c;而WebSocket作为一种全双工通信协议&#xff0c;为实现实时、高效的消息推送提供了全新的解决方案。 在Web开发…

基于单片机微波炉加热箱系统设计

**单片机设计介绍&#xff0c; 基于单片机微波炉加热箱系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机的微波炉加热箱系统是一种智能化的厨房电器设备&#xff0c;利用单片机控制技术实现自动加热和定时等功能…

unity 使用Vuforia扫描实体物体交互

文章目录 前言一、Vuforia是什么&#xff1f;二、Unity导入Vuforia1.去Unity - Windows – Asset Store&#xff0c;搜vuforia engine&#xff0c;添加到我的资源2.从 Unity 的菜单 Assets -> Import package -> Custom Package 导入脚本&#xff0c;添加 Vuforia Engine…