这是一棵适合搜索二叉树

🎈个人主页:🎈 :✨✨✨初阶牛✨✨✨
🐻强烈推荐优质专栏: 🍔🍟🌯C++的世界(持续更新中)
🐻推荐专栏1: 🍔🍟🌯C语言初阶
🐻推荐专栏2: 🍔🍟🌯C语言进阶
🔑个人信条: 🌵知行合一
🍉本篇简介:>:"二叉搜索树"的模拟实现
金句分享:
✨远赴人间惊鸿宴,一睹人间盛世颜!✨

目录

  • 一、什么是"二叉搜索树"?
  • 二、"二叉搜索树"的实现
    • 结点结构
    • "二叉搜索树":的结构
    • (1) "插入"操作
    • (2) "查找"操作
    • (3) "删除"操作 (重点)
    • (4)"中序"遍历
  • 三、结语

一、什么是"二叉搜索树"?

二叉搜索树(Binary Search Tree)又称为二叉查找树,是一种常用的数据结构。它是一棵空树,或者是具有以下性质的二叉树:

  1. 左子树上所有结点的值都小于它的根结点的值。
  2. 右子树上所有结点的值都大于它的根结点的值。
  3. 左右子树也分别为二叉搜索树。

错误示例1:
在这里插入图片描述
错误示例2:
在这里插入图片描述

正确示例:
在这里插入图片描述

二、"二叉搜索树"的实现

本篇文章实现的是键值对也就是(key,value)版本的 “二叉搜索树”.
Key-value版本的二叉搜索树(BST)是一种基于二叉树数据结构的数据结构,其中每个节点都存储一个键-值对。在该数据结构中,每个节点都具有一个唯一的关键字,该关键字用于对节点进行排序.

如下是树中每个结点的结构:

结点结构

template<class K, class V>
struct BSTreeNode
{
	BSTreeNode(const K& key = K(), const V& value = V())
		: _left(nullptr), _right(nullptr), _key(key), _value(value)
	{}
	BSTreeNode<K,V>* _left;
	BSTreeNode<K,V>* _right;
	K _key;							//关键码,用于比较大小的,按key比较
	V _value;						//用于存储数据
};

“二叉搜索树”:的结构

template<class K, class V>
class BSTree
{
	typedef BSTreeNode<K, V> Node;		//注意这里的类型重命名
public:
	bool Insert(const K& key, const V& value);
	Node* Find(const K& key);
	bool Erase(const K& key);
	void _InOrder(Node* root);
	void InOrder();
private:
	Node* _root = nullptr;
};

(1) "插入"操作

根据"二叉搜索树"的特性,我们不难知道,左子树 < 根 < 右子树.

  1. 如果是空树,则表明新插入的结点将作为根节点.
  2. 如果不是空树,则先找到该插入的位置,再链接即可.

示例:如果在插入一个结点值为9的结点.

寻找过程:
比根节点8大,所以往右找.
12小,所以往左找.
11小,所以往左找.
11的左边为空,寻找结束.

9结点插入到11的左边.

在这里插入图片描述

//插入操作
template<class K, class V>
bool BSTree<K,V>::Insert(const K& key, const V& value)
{
	//如果是空树,则直接赋值给根节点
	if (_root == nullptr)
	{
		//没看清node结构的,可以翻到上面在看一下构造函数.
		_root = new Node(key,value);	//用值构建结点,并赋给根节点
		return true;
	}

	//如果不是 空树
	Node* cur = _root;			//代替根节点遍历树,寻找插入位置.
	Node* pnode = nullptr;		//记录目标位置的父亲结点
	while (cur)				//一直找到nullptr为止
	{
		pnode = cur;				//更新父节点
		if (key > cur->_key)		//如果插入的键值对 key 比当前元素的key大,则往右走
		{
			cur = cur->_right;
		}
		else if (key < cur->_key)		//如果插入的值比当前元素小,则往左走
		{
			cur = cur->_left;
		}
		else return false;			//相等则返回false
	}

	//判断插入在左边还是右边
	Node* newnode = new Node(key, value);
	if (key < pnode->_key)
	{
		pnode->_left = newnode;
	}
	else
	{
		pnode->_right = newnode;
	}
	return true;
}

(2) "查找"操作

友友们插入操作都能轻松拿捏,那find还不是轻松拿捏?

小注意:
如果函数是在类里面声明,类外面定义,则需要注意一个小问题.
Node是一个类型还是一个变量(静态成员变量可以通过类名+ ::访问),所以需要在前面加上一个关键字typename ,告诉编译器这是一个类型.

template<class K, class V>
typename BSTree<K,V>::Node* BSTree<K, V>::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 cur;			//相等则说明找到了
	}
	return nullptr;
}

(3) "删除"操作 (重点)

删除操作应该是"二叉搜索树"最难的操作了,这里牛牛就讲解的仔细一点吧!

(1)情况1: 目标结点没有孩子.
处理方法:找到该结点 和 该结点的父亲,直接删除即可.
在这里插入图片描述

(2)情况2:目标结点只有一个孩子,可能是左孩子,也可能是右孩子.
处理方法:
只有左孩子时:
让父亲不再指向自己(这里要判断自己在父亲的左还是右),而是指向自己的左孩子,然后再删除自己.
在这里插入图片描述

只有右孩子时:
让父亲不再指向自己(这里要判断自己在父亲的左还是右),而是指向自己的右孩子,然后再删除自己.
在这里插入图片描述

情况3: 目标结点有两个孩子.

右子树最小节点:
在这里插入图片描述

左子树最大节点:
在这里插入图片描述

代码实现:

template<class K, class V>
bool BSTree<K, V>::Erase(const K& key)
{
	if (_root == nullptr)
	{
		cout << "空树不可删除" << endl;//空树无法删除
		return false;
	}

	//寻找目标结点的位置

	Node* pnode = nullptr;		//记录当前结点的父亲结点
	Node* cur = _root;			//当前结点:代替根节点遍历树.

	//寻找目标结点
	while (cur)
	{

		if (key > cur->_key)		//如果插入的值比当前元素大,则往右走
		{
			pnode = cur;
			cur = cur->_right;
		}
		else if (key < cur->_key)		//如果插入的值比当前元素小,则往左走
		{
			pnode = cur;
			cur = cur->_left;
		}
		else  break;			//相等则说明找到了
	}

	//表示在树中 未找到
	if (cur == nullptr) { return false; }
	
	//这里采取与右子树的最小结点替换的方法
	if (cur->_right && cur->_left)//如果有两个孩子
	{
		Node* p_left_max = nullptr;			//右树 的最小节点的父亲
		Node* left_max = cur->_right;		//右树 的最小节点
		//寻找目标结点 右树 的最小节点
		while (left_max->_left)
		{
			p_left_max = left_max;
			left_max = left_max->_left;
		}
		//替换
		cur->_key = left_max->_key;		//其实覆盖即可
		cur->_value = left_max->_value;

		//将原右子树的最小结点的父亲与 右树最小结点断绝关系
		p_left_max->_left = nullptr;
		delete left_max;					//删除右树最小结点
		return true;
	}

	// 要删除的节点只有一个子节点或没有子节点
	Node* child = nullptr;
	//这样child就是孩子
	if (cur->_left)	//只有左孩子
	{
		child = cur->_left;
	}
	else//只有右孩子或者没有孩子
	{
		child = cur->_right;
	}

	if (pnode == nullptr) // 根节点要删除的情况
		_root = child;
	else if (pnode->_left == cur) // 要删除的节点是父节点的左子节点
		pnode->_left = child;
	else // 要删除的节点是父节点的右子节点
		pnode->_right = child;
	delete cur;
	return true;
}

(4)"中序"遍历

学过二叉树的友友,对于这个,没啥好说的吧.

补充小技巧.

由于我们在类外面调用中序遍历函数需要传递root结点,但是root结点是私有成员变量,在类外面无法获取.
对象名.InOrder();

优秀的解决方法:
再嵌套一层,类里面的函数可以直接获取私有成员变量root,所以我们可以利用这一点.

template<class K, class V>
void  BSTree<K, V>::InOrder()
{
	if (_root == nullptr)
	{
		cout << "空树" << endl;
		return;
	}
	_InOrder(_root);		//这里调用即可
}

类中:

template<class K, class V>
class BSTree
{
	typedef BSTreeNode<K, V> Node;
public:

	void _InOrder(Node* root);
	void InOrder();
private:
	Node* _root = nullptr;
};

真正的中序遍历:


template<class K, class V>
void  BSTree<K, V>::_InOrder(Node* root)
{
	if (root == nullptr)return;
	_InOrder(root->_left);
	cout << root->_key << "->" << root->_value << endl;
	_InOrder(root->_right);
}

三、结语

好的,到这里二叉搜索树就实现完毕了,二叉搜索树可是很优秀的一种数据结构呢!
搜索数据的时间复杂度在O(logn)级别,因为每判断一次,就可以舍去一半的子树(大往右子树找,小往左子树找),这样就是高度层.

当然,搜索二叉树也是有明显的缺点的,到时候我们在AVL树中介绍吧!

在这里插入图片描述

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

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

相关文章

Tesco EDI需求分析

Tesco&#xff0c;成立于1919年&#xff0c;是一家全球领先的综合性零售企业&#xff0c;总部位于英国。公司致力于提供高质量、多样化的商品和服务&#xff0c;以满足客户的需求。Tesco的使命是通过创新和卓越的客户服务&#xff0c;为客户创造更美好的生活。多年来&#xff0…

【带你读懂数据手册】CN3702 一款锂电池充电芯片

大家在学习智能车或者飞行器的时候&#xff0c;是不是外接一个电池&#xff1f;最近刚好学习了一款充电芯片&#xff0c;来和大家分享一下&#xff0c;也算是我的一点点笔记。 一款7.4V锂电池&#xff0c;基本上也满足了单片机的外设&#xff0c;如果需要12V或者24V的电压&…

【前端】前端监控⊆埋点

文章目录 前端监控分为三个方面前端监控流程异常监控常见的错误捕获方法主要是 try / catch 、window.onerror 和window.addEventListener 等。Promise 错误Vue 错误React 错误 性能监控用户行为监控常见的埋点方案来源 前端监控分为三个方面 异常监控&#xff08;监控前端页面…

软件临界资源访问冲突

1. 基础概念 1.1 cpu执行汇编代码 处理指令的步骤主要包括以下几步动作&#xff1a; 1.提取(Fetch)指令。 2.解码(Decode)指令。 3.执行(Execute)指令。 cpu运行一条汇编需要执行三个步骤&#xff0c;按照顺序依次执行。异常触发中断需要等待一条汇编运行完成才能跳转&…

数据库的基本概念以及MySQL基本操作

一、数据库的基本概念 1、数据库的组成 数据&#xff1a;描述事物的符号记录 包括数字&#xff0c;文字、图形、图像、声音、档案记录等 以“记录”形式按统一格式进行存储 表&#xff1a;将不同的记录组织在一起&#xff0c;用来存储具体数据 数据库&#xff1a; 表的集合…

Python 跨文件夹导入自定义包

一、问题再现 有时我们自己编写一些模块时&#xff0c;跨文件夹调用会出现ModuleNotFoundError: No module named XXX 二、解决方案 只需要在下层文件夹中的__init__.py文件中&#xff0c;添加如下代码即可&#xff1a; import sys from os import path sys.path.append(pa…

万字解析设计模式之 适配器模式

一、 适配器模式 1.1概述 将一个接口转换成客户希望的另一个接口&#xff0c;适配器模式使接口不兼容的那些类可以一起工作。 适配器模式分为类适配器模式和对象适配器模式&#xff0c;前者类之间的耦合度比后者高&#xff0c;且要求程序员了解现有组件库中的相关组件的内部结…

imx VPU解码分析4-wrap与hantro的关系

前面已经分析了wrap和hantro&#xff0c;但是二者是如何结合的&#xff0c;wrap是如何封装hantro的&#xff0c;提供了哪些接口&#xff0c;封装了哪些细节还不太清楚&#xff0c;此文来探究下。这里还是只关注解码。 imx VPU解码分析1-wrap-CSDN博客 imx VPU解码分析2-hantr…

值得收藏推荐的 21 款免费数据恢复软件工具

使用这些免费数据恢复工具 之一找回您认为永远消失的文件。我根据这些程序的易用性和提供的功能对这些程序进行了排名。 这些应用程序从您的硬盘驱动器、USB 驱动器、媒体卡等恢复文档、视频、图像、音乐等。我建议每个计算机所有者安装其中一个程序&#xff0c;最好尽快&#…

【MySQL】一些内置函数(时间函数、字符串函数、数学函数等,学会了有妙用)

内置函数 前言正式开始时间函数显示当前日期、时间、日期时间的日期计算相差多少天示例创建一张表&#xff0c;记录生日 留言表 字符串函数charsetconcatinstr(string, substring)ucase和lcaseleft(string, length)length求字符串长度replace(str, search_str, replace_str)tri…

【LeetCode刷题笔记】DFSBFS(一)

51. N 皇后 解题思路: DFS + 回溯 :由于 NxN 个格子放 N 个皇后, 同一行不能放置 2 个皇后,所以皇后必然放置在不同行 。 因此,可以从第 0 行开始,逐行地尝试,在每一个 i

Pyside6/PyQt6的QTreeWidget如何添加多级子项,如何实现选中父项,子项也全部选中功能,源码示例

文章目录 📖 介绍 📖🏡 环境 🏡📒 使用方法 📒📝 数据📝 源码📖 介绍 📖 在UI开发中经常会需要展示/让用户多层级选择,这篇文章记录了一个QTreeWidget如何添加多级子项,如何实现选中父项,子项也全部选中/取消选中功能的源码示例,大家可以举一反三实现自…

合理运用ChatGPT使用Python编写一个桌面便签应用

ChatGPT的编程能力也不差&#xff0c;本次我就一步一步提要求&#xff0c;让ChatGPT根据我的要求&#xff0c;编写出一个可用的&#xff0c;可打包运行的桌面便签。 代码 import sys from PyQt5.QtWidgets import QApplication, QMainWindow, QMenu, QAction, QSystemTrayIco…

php一句话木马免杀

php一句话木马免杀 针对于php一句话木马做免杀&#xff1a; 利用php动态函数的特性&#xff0c;将危险函数拆分成字符&#xff0c;最终使用字符串拼接的方式&#xff0c;然后重新拼接&#xff0c;后加括号执行代码&#xff0c;并且可以使用花指令进行包装&#xff0c;如无限i…

Unity收费对谁影响最大

Unity的收费政策对以下几类人群影响最大&#xff1a; 游戏开发商&#xff1a;Unity收费政策中最直接的影响对象就是游戏开发商。对于那些使用Unity引擎制作游戏的开发商来说&#xff0c;他们将需要考虑新的许可证费用和服务费用&#xff0c;这可能会对他们的盈利和发展产生影响…

springboot项目基于jdk17、分布式事务seata-server-1.7.1、分库分表shardingSphere5.2.1开发过程中出现的问题

由于项目需要&#xff0c;springboot项目需基于jdk17环境开发&#xff0c;结合nacos2.0.3、分布式事务seata-server-1.7.1、分库分表shardingSphere5.2.1等&#xff0c;项目启动过程中出现的问题解决方式小结。 问题一&#xff1a; Caused by: java.lang.RuntimeException: j…

C++ LibCurl实现Web指纹识别

Web指纹识别是一种通过分析Web应用程序的特征和元数据&#xff0c;以确定应用程序所使用的技术栈和配置的技术。这项技术旨在识别Web服务器、Web应用框架、后端数据库、JavaScript库等组件的版本和配置信息。通过分析HTTP响应头、HTML源代码、JavaScript代码、CSS文件等&#x…

【Mysql系列】LAG与LEAD开窗函数

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

visionOS空间计算实战开发教程Day 5 纹理和材质

在​​Day 4​​​中我们使用了​​ImmersiveSpace​​并在其中添加了一个立方体&#xff0c;但对这个立方体我们只配置了长宽高&#xff0c;并没有做进一步的操作。 本文中我们会通过纹理和材质对这个立方体的六个面分别进行不同的绘制。首先我们将​​ImmersiveView​​分拆…

Redis入门与应用

目录 Redis的技术全景 两大维度 三大主线 Redis的版本选择与安装 Redis的linux安装 Redis的启动 默认配置 带参数启动 配置文件启动 操作 停止 Redis全局命令 键名的生产实践 Redis常用数据结构 字符串&#xff08;String&#xff09; 操作命令 set 设置值 g…