C++中二叉搜索树的底层原理及实现

news2024/12/24 21:37:48

小编在学习完二叉搜索树(SearchBinaryTree)之后觉得虽然二叉搜索树不是很难,但是它对于后面学习C++中的AVL树和红黑树及map和set的封装都有重要的作用,因此小编今天带给大家二叉搜索树的原理及实现,话不多说,开始学习!~~~

一、二叉搜索树的底层原理

1、二叉搜索树的图片(回顾二叉树)

通过上面一张图片大家可能不能得出二叉搜索树的原理,话不多说,直接告诉大家

原理:任意一个节点的左子树的值小于这个节点的值,并且这个节点的右子树的值大于这个节点的值。通俗理解也就是小于这个节点的值放到这个节点的左边,大于这个节点的值放到这个节点的右边即可。

大家通过在C语言数据结构中学习的二叉树的基础,肯定可以明白这个二叉搜索树的结构,因为二叉搜索树和二叉树的树形结构一样,但是稍微不一样的点就是二叉搜索树有自己的原理,不只是简单的储存数据,二叉搜索树储存的数据都是有特定的意义,并且都符合二叉搜索树的原理才可以。

二、二叉搜索树的实现

1、在了解了二叉树的原理内容后,大家先跟小编写一个二叉搜索树的基础结构

	// 搜索二叉树 左边节点小于父节点,右边节点大于父节点
	template<class T>
	struct BSTreeNode
	{
		T _data;
		struct BSTreeNode<T>* _left;
		struct BSTreeNode<T>* _right;

		BSTreeNode(const T& data)
			:_data(data)
			, _left(nullptr)
			, _right(nullptr)
		{}
	};

上面就是一个二叉树的基本结构,相信大家并不陌生,并且每次创建的节点让左右节点的指针都置为空。

2、第二步,大家跟着小编实现一个二叉树的插入功能,在这里,大家应该已经明白了二叉搜索树的原理,左节点的值小于父节点,右节点的值大于根节点,因此大家可以根据插入的值的大小来判断这个这个插入的值应该插入到哪块。接下来,画图给大家介绍一个例子,大家肯定秒懂:

每次插入的值,直到比较到空节点的位置就是该值插入的位置,再明白了这个插入原理之后相信大家应该可以实现一个插入的模块了,小编已经实现完啦,将代码和注释放到下面,供大家参考:

		typedef struct BSTreeNode<T> Node;     // 由于节点的名字太长,所以在这里我重命名一下
		bool Insert(const T& x)
		{
			if (_root == nullptr)
			{
				_root = new Node(x);
				return true;
			}
			Node* parent = nullptr;   // 用 parent 的二叉树的指针记录当前节点的父节点的指针,为插入节点做准备
			Node* cur = _root;        // cur 节点用来记录二叉树根节点的指针
			while (cur)
			{
				if (cur->_data < x)   // 插入的值比当前节点的值大,走到右边
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (cur->_data > x) // 插入的值比当前节点的值小,走到左边
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					// 走到else说明搜索二叉树里面有这个值,所以就不用插入
					return false;
				}
			}
			// 走到这块表达已经找到了插入的位置
			// 在插入时还应该判断当前节点应该插入到父节点的左还是右,判断完直接插入
			if (parent->_left == cur && parent->_data > x)
				parent->_left = new Node(x);
			else
				parent->_right = new Node(x);
		}

3、第三步大家请跟着我学习二叉树的节点删除操作,这个可就有点难啦,不过不影响,我来告诉大家原理和操作,画图及注释,大家肯定可以很清楚的学会这个删除节点的操作。不过在删除一个节点的时候需要明白这个节点是否存在,如果这个节点存在才有删除的必要,那这里就需要大家先实现一个查找模块,查找操作和插入操作有点相同,在这里小编就不仔细讲了,直接将代码及注释放到下面供大家参考及学习。

查找操作:

		bool Find(const T& x)
		{
			// 在这里查找二叉搜索树中是否存在 x 这个值
			Node* cur = _root;   // 先记录根节点方便遍历这颗二叉搜索树
			while (cur)
			{
				if (cur->_data < x)   // 如果这个 x 的值大于当前节点的值,就走向右侧
				{
					cur = cur->_right;
				}
				else if (cur->_data > x) // 如果这个 x 的值小于当前节点的值,就走向左侧
				{
					cur = cur->_left;
				}
				else
				{
					return true;       // 如果当前节点的值等于 x 及说明已经找到 返回true
				}
			}
			return false;              // 走到这块说明没有找到 ,返回false
		}

大家在实现删除节点的操作的时候需要先来理解下删除的原理,删除操作有四种情况,下面我分别画图来告诉大家这四种情况,还有这四种情况的处理方式,难度由易到难,放在下面:

情况一(删除的节点为叶子节点):

情况二(删除的节点不为叶子节点,并且存在一个子节点):

情况三(删除中间节点,并且当前节点有两个子节点):

情况四(删除中间节点,并且当前节点有两个子节点(不同于情况三)):

好了看到这块相信大家已经明白了删除操作的所有流程,那我把实现的过程代码及注释放到下面供大家参考:

		bool erase(const T& x)
		{
			assert(_root != nullptr);
			// 当只有一个根节点是特殊处理
			if (_root->_left == nullptr && _root->_right == nullptr)
			{
				delete _root;
				_root = nullptr;
				return true;
			}
			// 删除有两种情况
			// 1、删除的节点只有一个或者没有子节点
			// 2、删除的节点有两个节点
			Node* parent = nullptr;
			Node* cur = _root;
			while (cur)
			{
				// 第一种情况
				if (cur->_data > x)
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (cur->_data < x)
				{
					parent = cur;
					cur = cur->_right;
				}
				else
				{
					// 处理的特殊情况 根节点是需要消除的节点 并且只有一个子节点
					if (cur == _root && cur->_left == nullptr)
					{
						_root = _root->_right;
						delete cur;
						return true;
					}
					if (cur == _root && cur->_right == nullptr)
					{
						_root = _root->_left;
						delete cur;
						return true;
					}
					int flag = 0;
					// 这里是处理第一种情况和第二种情况,把他们放在一块处理,这里有一个空指针说明已经找到了叶子节点
					// 或者叶子节点的上一个节点,但是只有一个子节点
					if (parent->_right == cur && cur->_left == nullptr)
					{
						parent->_right = cur->_right;
						flag = 1;
					}
					if (parent->_left == cur && cur->_left == nullptr)
					{
						parent->_left = cur->_right;
						flag = 1;
					}
					if (parent->_right == cur && cur->_right == nullptr)
					{
						parent->_right = cur->_left;
						flag = 1;
					}
					if (parent->_left == cur && cur->_right == nullptr)
					{
						parent->_left = cur->_left;
						flag = 1;
					}
					if (flag == 1)
					{
						delete cur;
						return true;
					}
					break;
				}
			}
			// 如果是正常情况下退出循环,说明没有找到要删除的数据
			if (cur == nullptr)
				return false;
			// 第二种情况
			// 可以选择在左边找一个最大值,或者在右边找一个最小值来替代删除的元素
			// 这里实现的是从右边找最小的
			Node* pt = nullptr;
			Node* rt = cur->_right;
			// 这种情况是第四种情况
			if (rt->_left == nullptr)
			{
				// 说明这个rt的节点就是右边最小的节点
				swap(rt->_data, cur->_data);
				cur->_right = rt->_right;
				delete rt;
				return true;
			}
			// 走到这块说明rt不是右边最小的节点
			// 这个是第三种情况
			while (rt->_left)
			{
				pt = rt;
				rt = rt->_left;
			}
			swap(rt->_data, cur->_data);
			delete rt;
			pt->_left = nullptr;
			return true;
		}

4、第四步就只需要处理的是按照大小顺序打印这棵树的每个节点的值,先讲解打印操作的原理

操作原理:因为这棵树的排序方式是根节点大于左子树,根节点小于右子树,所以使用二叉树的中序遍历就可以把这颗树按照大小顺序打印出来。

具体操作和数据结构中二叉树的中序遍历相同,这里我就直接向大家展示代码及注释即可:

		// 查找二叉树的中序遍历刚好是顺序排序
		void InOrder()
		{
			// 中序遍历需要递归来解决,所以这个 _root 不好传 如果直接用_root 的话 _root 就会被改变
			Node* cur = _root;
			_InOrder(cur);
			cout << endl;
		}
	private:
		void _InOrder(Node* root)
		{
			if (root == nullptr)
				return;
			_InOrder(root->_left);
			cout << root->_data << " ";
			_InOrder(root->_right);
		}

		Node* _root = nullptr;

5、搜索二叉树的分析

如上图搜索二叉树可能会出现第二种情况,所以时间复杂度会大大提到,第二种情况的时间复杂度最后为O(N),但如果是接近平衡的搜索二叉树时间复杂度就会接近O(nlgn),大大提高效率,所以以搜索二叉树衍生除了AVL树和红黑树后面带给大家学习,就会解决这种一边倒的情况,让搜索二叉树接近平衡

好啦,今天的内容就到这啦,搜索二叉树的内容相信大家一定会有所收获,我们下期再见!~~~

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

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

相关文章

<Linux> git

在使用git之前&#xff0c;要先在linux中安装git yum list | grep git yum install -y 文件名 在第一次安装git时&#xff0c;需要进行下面的操作 git config --global user.email "你的邮箱名" git config --global user.name "你想要的名字" 1. git clon…

LeetCode 热题 HOT 100 (038/100)【宇宙最简单版】

【动态规划】No. 0337 打家劫舍III【中等】&#x1f449;力扣对应题目指路 希望对你有帮助呀&#xff01;&#xff01;&#x1f49c;&#x1f49c; 如有更好理解的思路&#xff0c;欢迎大家留言补充 ~ 一起加油叭 &#x1f4a6; 欢迎关注、订阅专栏 【力扣详解】谢谢你的支持&a…

NGINX 常用内置变量

目录 $remote_addr 变量 $args 变量 $is_args 变量 $document_root 变量 $document_uri 变量 $host 变量 $limit_rate 变量 $remote_port 变量 $remote_port --显示客户端端口 $request_method 变量 --返回请求方式 $request_filename 变量 --返回请求实际路径 $request_uri…

eNSP 华为ACL配置

华为ACL配置 需求&#xff1a;公司保证财务部数据安全&#xff0c;禁止研发部门和互联网访问财务服务器&#xff0c;但总裁办不受影响 R1&#xff1a; <Huawei>sys [Huawei]sys Router1 [Router1]undo info-center enable [Router1]int g1/0/0 [Router1-GigabitEthern…

AI 工程应用 建筑表面检测及修复

文章目录 1 项目概述&#xff08;必写&#xff09;&#xff1a;2 技术方案与实施步骤2.1 模型选择&#xff08;必写&#xff09;&#xff1a;2.2 数据的构建&#xff1a;2.3 功能整合&#xff08;进阶&#xff09;&#xff1a; 3 实施步骤&#xff1a;3.1 环境搭建&#xff08;…

【Nginx】nginx的核心配置

1.nginx的文件启动 [rootNginx ~]# vim /lib/systemd/system/nginx.service [Unit] DescriptionThe NGINX HTTP and reverse proxy server Aftersyslog.target network-online.target remote-fs.target nss-lookup.target Wantsnetwork-online.target [Service] Typeforking P…

Python -- GUI图形界面编程—GUI编程实例 博主也在持续学习中[ 持续更新中!!! 欢迎白嫖 也求粉啊啊啊~ ]

本文介绍了GUI的图形界面编程&#xff08;相关视频是哔站上的应该搜这个题目就能找到&#xff09;&#xff0c;文章还是很基础的&#xff0c;反正我是小白从0开始&#xff0c;主要的结构tinkter库、重要组件简介&#xff08;这个不用死记硬背 用的时候再说&#xff09;、Label&…

诊断知识:DTC Status中pending位的使用

文章目录 前言OCC6的定义pending位的定义pending位的使用总结 前言 上一篇文章介绍了ConfirmedDTCLimit的使用&#xff0c;诊断知识&#xff1a;ConfirmedDTCLimit的使用&#xff0c;后面发现理解还是有问题的&#xff0c;其实原来的图画的没有问题&#xff0c;之前对OCC6理解…

【业余玩儿AI】Day 1

【业余玩儿AI】Day 1 实际是昨天的事儿了&#xff0c;记录以下 魔法 不管三七二十一&#xff0c;重新启用魔法&#xff0c;没有魔法这些事情肯定是不行滴 种子任务 把收藏了两个星期的短视频都看了一遍&#xff0c;挑了个种子任务&#xff0c;《本地部署Llama3.1》&#x…

【Web IDE】WebContainer容器在浏览器中启动运行nodejs并使用vite启动项目

参考了文章WebContainer/api 基础&#xff08;Web IDE 技术探索 一&#xff09; 在浏览器中运行vite的vue3项目 示例站点 最终效果 主要流程 加载WebContainer》加载代码压缩包>解压代码压缩包》生成文件树》挂载文件树》pnpm安装依赖》启动项目 代码 <script setup…

Unity动画模块 之 3D模型导入基础设置Model页签

本文仅作笔记学习和分享&#xff0c;不用做任何商业用途 本文包括但不限于unity官方手册&#xff0c;unity唐老狮等教程知识&#xff0c;如有不足还请斧正​ 创建模型&#xff1a;在 Unity 外部创建模型 - Unity 手册 导入模型&#xff1a;将模型导入 Unity - Unity 手册 1.…

算法的学习笔记—二叉树的镜像(牛客JZ27)

&#x1f600;前言 在二叉树相关的问题中&#xff0c;镜像操作是一个非常经典且常见的题目。本文将通过一道具体的题目&#xff0c;详细讲解如何将一棵二叉树转换为它的镜像&#xff0c;并提供实现该操作的Java代码示例。 &#x1f3e0;个人主页&#xff1a;尘觉主页 文章目录 …

【LVGL9学习笔记-2.添加百问网demo至工程模板】

添加百问网demo至工程模板 上一节使用codeBlocks运行了LVGL ,以此作为模板&#xff0c;将百问网的一些demo添加至该工程中 拷贝文件与配置文件至该目录下 打开工程包含文件 –>add files recusively –>add files 编译一次出现如下情况&#xff08;后面学到了再做解决…

[机器学习]全景指南:从基础概念到实战流程的全面解析

文章目录 1.引言1.1机器学习的重要性1.2机器学习的应用范围1.3本文的内容结构 2. 机器学习的基本概念与分类2.1 机器学习的定义2.2 机器学习的分类 4. 强化学习&#xff08;Reinforcement Learning&#xff09; 3. 机器学习的工作流程3.1 数据收集与准备1. 数据源与类型2. 数据…

Windows SDK 消息类型详解

消息结构体 如下是消息的结构体 typedef struct tagMSG {HWND hwnd; // 消息所属窗口的句柄UINT message; // 消息的标识符&#xff0c;表示什么类型的消息&#xff0c;如WM_PAINT、WM_QUIT等。WPARAM wParam; // 与消息相关的附加信息。具体含义取决于消息的类型。L…

模型驱动设计(MODEL-DRIVEN DESIGN)

前言 为了保证软件实现得简洁并且与模型保持一致&#xff0c;不管实际情况如何复杂&#xff0c;必须运用建模和设计的最佳实践。 本书中的软件设计风格主要遵循"职责驱动设计"的原则&#xff0c;这个原则是Wirfs-Brock等人在1990年中提出的&#xff0c;并在2003年进…

Nios ll软核处理器

1.打开软件 &#xff08;1&#xff09;quartus软件内部打开 &#xff08;2&#xff09;创建软件快捷方式 软件安装目录文件夹下&#xff0c;nois2eds -> bin -> eclipse-nios2.exe &#xff0c;创建快捷方式&#xff0c;放到桌面&#xff0c;双击打开软件。 2.Workspa…

CSC2121 半桥驱动芯片

CSC2121 X系列是一款高性价比的半桥架构的栅极驱动专用电路&#xff0c;用于大功率MOS管、IGBT管栅极驱动。CSC2121内部集成了逻辑信号处理电路、死区时间控制电路、欠压保护电路、电平位移电路、脉冲滤波电路及输出驱动电路&#xff0c;CSC2121X专用于无刷电机控制器中驱动电路…

韩国服务器的性能如何提升

韩国服务器的性能可以通过硬件升级、网络优化、缓存优化和软件优化来提升。具体方法如下&#xff0c;rak小编为您整理发布韩国服务器的性能如何提升。 1. 硬件升级 CPU升级&#xff1a;选择高性能的多核处理器&#xff0c;可以显著提升计算速度和响应能力。 内存升级&#xff1…

9.内置函数

目录 1.日期函数 案例1&#xff1a; 创建一张表&#xff0c;记录生日 案例2&#xff1a; 创建一个留言表 2.字符串函数 charset案例1->返回字符串字符集 concat案例连接字符串 instr案例 ​编辑 ucase案例 lcase案例 left案例 length案例 replace案例 strcmp…