Cpp二叉搜索树的讲解与实现(21)

文章目录

  • 前言
  • 一、二叉搜索树的概念
    • 定义
    • 特点
  • 二、二叉树的实现
    • 基本框架
    • 查找
    • 插入
    • 删除
      • 当只有0 ~ 1个孩子的时候
      • 当有2个孩子的时候
  • 三、二叉树的应用
    • K模型
    • KV模型
  • 四、二叉树的性能分析
  • 总结


前言

这是全新的一个篇章呢,二叉搜索树是我们接下来学习set、map的前提
迈过它吧,关关难过关关过!

正文开始!


一、二叉搜索树的概念

定义

  二叉搜索树(Binary search tree)是基于二叉树的一种改进版本。因为 普通二叉树没有实际价值,无法进行插入、删除等操作(无意义),但二叉搜索树就不一样了,二叉搜索树对于数据的存储有严格要求:左节点比根小,右节点比根大

因此 二叉搜索树 的查找效率极高,具有一定的实际价值

  所以将数据存入 二叉搜索树 中进行查找时,理想情况下只需要花费 logN 的时间(二分思想)

  这就是 二叉搜索树 名字的由来,搜索(查找)速度很快

特点

  二叉树的基本特点:左比根小,右比根大

  1. 若某个节点的 左 节点不为空,则 左 节点的值一定比当前节点的值 小,且其 左 子树的所有节点都比它 小
  2. 若某个节点的 右 节点不为空,则 右 节点的值一定比当前节点的值 大,且其 右 子树的所有节点都比它 大
  3. 二叉搜索树的每一个节点的 根、左 、右 都满足基本特点

另外,中序遍历的结果为升序

二、二叉树的实现

二叉搜索树的源代码

基本框架

  跟普通的二叉树一样,二叉搜索树也需要节点类,同时将节点指针作为二叉搜索树的成员变量

template <class K>
struct BSTNode
{
	K _key;
	BSTNode<K>* _left;
	BSTNode<K>* _right;

	BSTNode(const K& key = K())
		:_key(key)
		,_left(nullptr)
		,_right(nullptr)
	{}

};

template <class K>
class BSTree
{
	typedef BSTNode<K> Node;
public:
	BSTree()
		:_root(nullptr)
	{}
	
private:
	Node* _root;
};

查找

  得益于二叉搜索树的特性,我们可以比较插入数字和当前节点的值,当比当前节点大的时候的时候往右走,反之则往左,若 cur 为空,那么返回 false ,若找到,则返回 true
在这里插入图片描述

bool Find(const K& key)
{
	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;
}

插入

  插入其实过程和查找差不多,只不过如果中途找到了就返回 false 表示二叉搜索树中已经有该数字,如果成功走到空了就开始插入
在这里插入图片描述

  只不过我们需要注意,当搜索树为空树的时候,我们必须新建立一个节点,将指针赋给这个根节点,另外,我们需要申请一个指针变量 parent 来记录父节点,方便后续链接

bool Insert(const K& key)
{
	// 如果为空树,则直接建立一个节点
	// 将其地址存放在_root上
	if (_root == nullptr)
	{
		_root = new Node(key);
		return true;
	}

	Node* parent = nullptr;
	Node* cur = _root;

	// 一直循环到cur为空
	while (cur) {
		if (key > cur->_key) {
			parent = cur;
			cur = cur->_right;
		}
		else if (key < cur->_key) {
			parent = cur;
			cur = cur->_left;
		}
		else {
			// 如果中途发现BS树已有key,则插入失败
			// BS树中没有重复元素,依据定义
			return false;
		}
	}

	// 此时建立一个节点,将其地址赋值给cur
	cur = new Node(key);

	// 此时需要根据值的大小来判断parent左链还是右链
	if (key > parent->_key)
		parent->_right = cur;
	else parent->_left = cur;

	return true;
}

删除

  删除可就复杂了,要考虑很多情况!

  我们发现如果我们要删除一个节点,并且在二叉搜索树中已经确定找到了该节点,可能有三种情况:

  1. 该节点没有孩子,即要删除的是叶子节点
  2. 该节点只有一个孩子,可能是左孩子为空,也有可能是有孩子为空
  3. 该节点有两个孩子,这种情况比较复杂,要考虑比较复杂的情况

当只有0 ~ 1个孩子的时候

  我们先来看第二种情况,当找到要删除的节点且该节点只有一个孩子后,此时显然父节点链上当前节点的子节点就可以了,这样不会破坏二叉搜索树的结构

二叉搜索树的右子树的值一定大于该节点的值,同样的,左子树的值一定小于该节点的值

  于是,我们就想着再销毁当前节点之前,先判断是父节点的左边链接还是右边链接,这很简单,我们检查一下 parent 左右指针哪个指向 cur 就行,同时,我们也要思考一下子节点与 cur 的链接关系,很简单,这也是直接判断一下就可以

其实,这种方法囊括了第一第二种情况,你可以思考一下为什么

// 如果左孩子为空
// 这时候就要parent就要链到cur的右边去
if (cur->_left == nullptr) {
	if (parent->_left == cur)
		parent->_left = cur->_right;
	else parent->_right = cur->_right;

	// 删除
	delete cur;
	cur = nullptr;

	return true;
}

// 如果右孩子为空
// 这时候就要parent就要链到cur的左边去
else if (cur->_right == nullptr) {
	if (parent->_left == cur)
		parent->_left = cur->_left;
	else parent->_right = cur->_left;

	// 删除
	delete cur;
	cur = nullptr;

	return true;
}

右子树为空
在这里插入图片描述

左子树为空
在这里插入图片描述

当有2个孩子的时候

在这里插入图片描述

  当左右孩子节点都不为空的时候,我们也要想想,万一把 cur 给删掉了,要换那一个替上来?

  关于这个问题,我们还是要把握二叉搜索树的一个核心特性,就是左子树所有节点的值一定比根节点小,右子树所有节点的值一定比根节点大

  那么只要将左子树最大的值和右子树最小的值找到,此时我们又要想,将两个其中之一的值替代父节点的值即可,然后再销毁节点,那么这样会不会破坏二叉树的结构呢?

  显然不会,只要能正确销毁并正确链接,那么就没关系,在这里我们选择找到右子树的最小值,这很简单,因为一个二叉搜索树的最小值就是最左边那个,那么我们同样用 rightMin 标记右子树的最小节点 ,用 rightMinP 标记其父节点,又为了防止 rightMinP 进不去循环,我们给 rightMinP 赋值 cur


Node* rightMinP = cur;
Node* rightMin = cur->_right;

// 找到右子树的最小节点
while (rightMin->_left) {
	rightMinP = rightMin;
	rightMin = rightMin->_left;
}

// 替代,这时候转换成就要删除rightMin这个节点了
// 这个时候就需要有它的父亲
cur->_key = rightMin->_key;

// 因为rightMin必然是最左节点
// 所以rightMinP必然是链接rightMin的右孩子
// 同时rightMinP是左链还是右链这不确定,需要判断一下
if (rightMinP->_left == rightMin)
	rightMinP->_left = rightMin->_right;
else rightMinP->_right = rightMin->_right;

delete rightMin;
rightMin = nullptr;

return true;

  但是但是!!我们发现写到这里后,当删到最后只剩几个节点之后,报错了!

  我们再回看代码,发现我们的逻辑是先找到删除节点,再用父亲节点去链接当前节点的子节点,关键是,有没有可能一开始我们就找到了要删除的节点,父亲节点没进循环,也就是说,没有父亲节点,这很不好,针对这种情况,我们就要移动根节点

if (parent == nullptr) {
	_root = _root->_right;

	delete cur;
	cur = nullptr;
	return true;
}

三、二叉树的应用

K模型

  K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值

我们上述代码实现的也就是这种

  举个例子,给一个单词word,判断该单词是否拼写正确,具体方式如下:

  1. 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
  2. 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误

KV模型

  每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对

  比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对

  再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对

KV模型也就是在K模型的基础上加上value的值,请试试吧!

四、二叉树的性能分析

  如果我问你二叉搜索树的查找时间复杂度为多少,你可能会不假思索的回答出是O(logN),但是,假如我给一个递减数列呢,是不是就退化成单支树了?

  所以理想状态下是O(logN),最坏情况下是O(N)


总结

  看了性能分析,你可能会想怎么让二叉树的性能达到最优?不急,AVL树和红黑树已经在路上了!~

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

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

相关文章

群控系统服务端开发模式-应用开发-菜单功能开发

为什么优先开发菜单&#xff0c;而不是优先开发管理员&#xff1f;查看一下程序草图就明白&#xff0c;还有一个重点就是&#xff0c;管理员需要添加图片&#xff0c;而我还没有封装上传工具及上传目标。 一、添加路由 在根目录下route文件夹下的app.php文件里面&#xff0c;添…

2024年,Rust开发语言,现在怎么样了?

Rust开发语言有着一些其他语言明显的优势&#xff0c;但也充满着争议&#xff0c;难上手、学习陡峭等。 Rust 是由 Mozilla 主导开发的通用、编译型编程语言&#xff0c;2010年首次公开。 在 Stack Overflow 的年度开发者调查报告中&#xff0c;Rust 连续多年被评为“最受喜爱…

c# 值类型

目录 1、c#类型2、值类型2.1 结构体2.2 枚举 1、c#类型 类型&#xff08;Type&#xff09;又叫数据类型&#xff08;Data Type&#xff09;。 A data type is a homogeneous collection of values,effectively prensented,equipped with a set of operations which manipulate…

怒刷666条提示词后,终于总结出终结 AI 味儿的3种方法(强烈建议收藏)

大家好&#xff0c;我是凡人。 最近一个朋友给我发了几张他知乎的评论&#xff0c;是这样的&#xff1a; 这样的&#xff1a; 还有这样的&#xff1a; 无奈他就问我 “ AI 怎么能生成没有 AI味儿 的回答&#xff1f;” &#xff0c;说实话这真是个神奇的问题。 老话儿讲 “ 耳…

Angular实现gridview效果

说明&#xff1a;使用angular实现grid效果&#xff0c;支持文字图片多条数据展示 效果图: step1: <mat-grid-list cols"2" rowHeight"2:1"><mat-grid-tile *ngFor"let course of courses">{{ course }}</mat-grid-tile> &l…

2、顶点着色器之视图矩阵

1、作用&#xff1a;将物体从世界坐标系转换到相机坐标系&#xff0c;相当于从世界坐标系转换到相机的局部(本地)坐标系。 2、基于LookAt函数的视图矩阵&#xff1a; 相机位置eye&#xff1a;(ex,ey,ez)&#xff0c;世界坐标系下的位置 目标位置center&#xff1a;(cx,cy,cz…

react使用Fullcalendar 实战用法

使用步骤请参考&#xff1a;react使用Fullcalendar 卡片式的日历&#xff1a; 需求图&#xff1a; 卡片式的日历&#xff0c;其实我是推荐 antd的&#xff0c;我两个都写了一下都能实现。 antd 的代码&#xff1a; antd的我直接用的官网示例&#xff1a;antd 日历示例 i…

基于SpringBoot+Vue实现智能停车收费系统

作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、多年校企合作经验&#xff0c;被多个学校常年聘为校外企业导师&#xff0c;指导学生毕业设计并参与学生毕业答辩指导&#xff0c;…

ApsaraMQ Serverless 能力再升级,事件驱动架构赋能 AI 应用

本文整理于 2024 年云栖大会阿里云智能集团高级技术专家金吉祥&#xff08;牟羽&#xff09;带来的主题演讲《ApsaraMQ Serverless 能力再升级&#xff0c;事件驱动架构赋能 AI 应用》 云消息队列 ApsaraMQ 全系列产品 Serverless 化&#xff0c;支持按量付费、自适应弹性、跨可…

基于SpringBoot+Vue实现高校毕业与学位资格审查系统

作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、多年校企合作经验&#xff0c;被多个学校常年聘为校外企业导师&#xff0c;指导学生毕业设计并参与学生毕业答辩指导&#xff0c;…

0.STM32F1移植到F0的各种经验总结

1.结构体的声明需放在函数的最前面 源代码&#xff1a; /*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启USART1的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructu…

RDT——清华开源的双臂机器人扩散大模型:先预训练后微调,支持语言、图像、动作多种输入

第一部分 清华开源全球最大双臂机器人扩散大模型RDT 2.1 什么是RDT 2.1.1 RDT推出的背景及其与以前工作的对比 受到最近在单手操作方面尝试的启发&#xff08;Brohan等&#xff0c;2023&#xff1b;Kim等&#xff0c;2024&#xff09;&#xff0c;清华一研究团队推出了RDT&a…

C++基础三(构造函数,形参默认值,函数重载,单例模式,析构函数,内联函数,拷贝构造函数)

C有六个默认函数&#xff0c;分别是&#xff1a; 1、默认构造函数; 2、默认拷贝构造函数; 3、默认析构函数; 4、赋值运算符; 5、取址运算符; 6、取址运算符const; 构造函数 构造函数(初始化类成员变量)&#xff1a; 1、属于类的成员函数之一 …

「DSA」C++排序算法 之 选择排序_Selection Sort_O(n²)

✨博客主页何曾参静谧的博客&#x1f4cc;文章专栏「C/C」C/C程序设计&#x1f4da;全部专栏「VS」Visual Studio「C/C」C/C程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasoli…

完美洗牌的秘密(十三)——(反)完美洗牌第二定理的应用(16张的Anti faro周期魔术)...

早点关注我&#xff0c;精彩不错过&#xff01; 在前面的文章中&#xff0c;我们介绍了&#xff08;反&#xff09;完美洗牌定理的应用的海量魔术&#xff0c;详情请戳&#xff1a; 完美洗牌的秘密&#xff08;十二&#xff09;——反完美洗牌定理的应用扩展&#xff08;三叠发…

TOEIC 词汇专题:旅游计划篇

TOEIC 词汇专题&#xff1a;旅游计划篇 制定旅行计划时&#xff0c;尤其是跨国旅游&#xff0c;会涉及到很多独特的英语词汇。以下是与“旅游计划”相关的托业词汇&#xff0c;帮助你更加自如地规划行程。 1. 旅行服务和优惠 出发前了解一下与服务和优惠相关的常用词汇&#…

PVE定时开启关闭虚拟机,实现PVE中群晖虚拟机的定时开机和关闭

如果在PVE中安装了群晖&#xff0c;又不想每天关闭PVE(不在家&#xff0c;怕服务器起不来)&#xff0c;因此想每天定时关闭开启黑群晖和其他虚拟机释放资源。 在网上查了很多&#xff0c;说在crontab添加命令 00 2 * * * pvesh create /nodes/pve/qemu/102/status/stop 00 6 …

JDBC/ODBC—数据库连接API概述

JDBC/ODBC概述 在数据库连接领域&#xff0c;有两种广泛使用的技术&#xff1a;ODBC&#xff08;Open Database Connectivity - 开放数据库连接&#xff09;和 JDBC&#xff08;Java Database Connectivity - Java 数据库连接&#xff09;。 一、什么是 ODBC&#xff1f; Ope…

2024年NSSCTF秋季招新赛-WEB

The Beginning F12看源码&#xff0c;有flag http标头 黑吗喽 题目说要在发售时的0点0分&#xff0c;所以添加标头data Date: Tue, 20 Aug 2024 00:00:00 GMT然后改浏览器头 User-Agent: BlackMonkey曲奇就是Cookie cookieBlackMonkey这个一般就是Referer Referer:wukon…

后台管理系统的通用权限解决方案(十一)SpringBoot的统一异常处理

文章目录 1 统一异常处理介绍2 统一异常处理案例 1 统一异常处理介绍 在实际项目中&#xff0c;不可避免需要处理各种异常。如果每个都单独处理&#xff0c;代码中则会出现大量的try {...} catch {...} finally {...}代码块&#xff0c;不仅有大量的冗余代码&#xff0c;而且还…