数据结构——“二叉搜索树”

news2024/12/28 20:54:10

        二叉搜索树是一个很重要的数据结构,它的特殊结构可以在很短的时间复杂度找到我们想要的数据。最坏情况下的时间复杂度是O(n),最好是O(logn)。接下来看一看它的接口函数的实现。

        为了使用方便,这里采用模版的方式:

一、节点

template <class K>
struct BSnode
{
	BSnode(K key)
		:_key(key)
	{}
	K _key;
	BSnode* _left = nullptr;
	BSnode* _right = nullptr;
};

        _key用来储存数据,_left和_right用来储存左子树和右子树的节点。

二、搜索树的类的定义

template <class K>
class BSTree
{
private:
	using Node = BSnode<K>;
	Node* _root = nullptr;
};

        这里typedef了BSnode<K>为Node的类型,方便使用。并创建了根节点,缺省值为空指针。

三、搜索树的插入

        搜索树的结构是左子树所有节点的值小于等于根结点的值,右子树所有节点的值大于等于根节点的值。在这里我不考虑等于的情况,即一棵树中不允许出现相同的值。代码如下:

	//搜索树的插入
	bool Insert(K& key)
	{
		Node* cur = _root;
		Node* parent = nullptr;
		if (cur == nullptr)
		{
			_root = new Node(key);
		}
		else
		{
			while (cur)
			{
				if (cur->_key < key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (cur->_key > key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					return false;
				}
			}

			if (key > parent->_key)//插入的值大于父亲节点,那么就需要在父亲节点的右边插入
			{
				parent->_right = new Node(key);
			}
			else//插入的值小于父亲节点,那么就需要在父亲节点的左边插入
			{
				parent->_left = new Node(key);
			}
		}
		return true;
	}

        代码大体情况如下:

        1.第一次插入数据的时候,根节点指向空,需要单独讨论。

        2.根节点不为空,那么就根据搜索树的特点找到最后插入的位置,申请新节点,连接新节点。

        3.找不到插入的位置,即插入的数据已经存在,返回false。

四、搜索树的查找

	//搜索树的查找
	Node* Find(const K& key)
	{
		assert(_root);
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key > key)
			{
				cur = cur->_left;
			}
			else if (cur->_key <  key)
			{
				cur = cur->_right;
			}
			else
			{
				return cur;
			}
		}

		return nullptr;
	}

        这段代码是根据搜索树的特点进行查找(搜索的值大于根,那么去右子树查找,小于则去左子树查找),倘若找到,返回该节点指针,找不到,返回空指针。

五、搜索树的删除

        搜索树的删除偏向复杂,在我写出的代码中大致分为以下几点:

        1.删除的数据在叶子节点上。

        2.删除的节点不在叶子节点上,但是它的左右节点至少有一个是空。

        3.删除的节点不在叶子节点上,且左右子树都不为空。

        4.删除的节点在根节点,根节点至少有一个为空。

通过总结可以精简以上条件:

        1.删除的节点的左右节点至少有一个为空。

        2.删除的节点的左右节点都不为空。

代码如下:

	//搜索二叉树的删除
	bool Erase(const K& key)
	{
		assert(_root);
		//先找到要删除的节点
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				break;
			}
		}

		//找不到要删除的数据,返回false
		if (cur == nullptr)return false;
		//找到了
		
//处理“该节点的左孩子或者右孩子为空,或者左右孩子均为空”
		if (cur->_left == nullptr || cur->_right == nullptr)
		{
			//该节点是根,且且根节点至少一个子树为空
			if (parent == nullptr)//parent为空,证明输入的是根节点,且根节点至少一个子树为空 
			{
				_root->_left == nullptr ? _root = _root->_right : _root = _root->_left;
				delete cur;
			}
			//该节点是左孩子
			else if (cur == parent->_left)
			{
				//左节点为空
				if (cur->_left == nullptr && cur->_right != nullptr)
				{
					parent->_left = cur->_right;
				}
				//右节点为空
				else if(cur->_left != nullptr && cur->_right == nullptr)
				{
					parent->_left = cur->_left;
				}
				//左右节点均为空
				else
				{
					parent->_left = nullptr;
				}
				
				//释放资源
				delete cur;
			}
			//该节点是右孩子
			else if (cur == parent->_right)
			{
				//左节点为空
				if (cur->_left == nullptr && cur->_right != nullptr)
				{
					parent->_right = cur->_right;
				}
				//右节点为空
				else if (cur->_left != nullptr && cur->_right == nullptr)
				{
					parent->_right = cur->_left;
				}
				//左右节点均为空
				else
				{
					parent->_right = nullptr;
				}

				//释放资源
				delete cur;
			}
		
		}
//处理“该节点的左右孩子均不为空”
		else
		{
			//找到左节点的最大值
			Node* Fparent = cur;
			Node* Fcur = cur->_left;
			


			while (Fcur ->_right)
			{
				Fparent = Fcur;
				Fcur = Fcur->_right;
			}
			//交换节点的值
			cur->_key = Fcur->_key;
			//Fcur的左边有数据
			if (Fcur->_left)
			{
				Fparent->_right = Fcur->_left;
				delete Fcur;
			}
			//Fcur的左边没有数据
			else
			{
				
				if(Fcur == Fparent->_left)
				{
					Fparent->_left = nullptr;
				}
				else
				{
					Fparent->_right = nullptr;
				}
				delete Fcur;
			}

		}
		
		return true;
	}

        首先是先要找到删除的数据,若找不到,返回false,若找到,那么进行下一步:

找到后还有如下情况:

        1.删除的节点的左右节点至少有一个为空。

        对应代码:

//处理“该节点的左孩子或者右孩子为空,或者左右孩子均为空”
		if (cur->_left == nullptr || cur->_right == nullptr)
		{
			//该节点是根,且且根节点至少一个子树为空
			if (parent == nullptr)//parent为空,证明输入的是根节点,且根节点至少一个子树为空 
			{
				_root->_left == nullptr ? _root = _root->_right : _root = _root->_left;
				delete cur;
			}
			//该节点是左孩子
			else if (cur == parent->_left)
			{
				//左节点为空
				if (cur->_left == nullptr && cur->_right != nullptr)
				{
					parent->_left = cur->_right;
				}
				//右节点为空
				else if(cur->_left != nullptr && cur->_right == nullptr)
				{
					parent->_left = cur->_left;
				}
				//左右节点均为空
				else
				{
					parent->_left = nullptr;
				}
				
				//释放资源
				delete cur;
			}
			//该节点是右孩子
			else if (cur == parent->_right)
			{
				//左节点为空
				if (cur->_left == nullptr && cur->_right != nullptr)
				{
					parent->_right = cur->_right;
				}
				//右节点为空
				else if (cur->_left != nullptr && cur->_right == nullptr)
				{
					parent->_right = cur->_left;
				}
				//左右节点均为空
				else
				{
					parent->_right = nullptr;
				}

				//释放资源
				delete cur;
			}
		
		}

        原理:由于删除的节点左右子树至少有一个为空,那么就可以让父节点继承被删除节点的非空节点。继承后,删除该节点。如果被删除的节点的左右子树都为空,即被删除的节点是叶子节点,那么就可以直接删除叶子节点,然后将父节点指向空。

        以上代码分别对删除的节点是左子树还是右子树的情况下,删除的节点是否有左子树,是否有右子树,还是左右子树都没有进行讨论,涵盖所有情况。值得注意的是,当搜索树呈现链状的时候,如果删除的是根节点,此时的父节点是空,不能进行访问,那么需要在这里单独讨论。将根节点移动到有数据的那个子节点。

2.删除的节点的左右节点都不为空。

        对应代码:

//处理“该节点的左右孩子均不为空”
		else
		{
			//找到左节点的最大值
			Node* Fparent = cur;
			Node* Fcur = cur->_left;
			while (Fcur ->_right)
			{
				Fparent = Fcur;
				Fcur = Fcur->_right;
			}
			//交换节点的值
			cur->_key = Fcur->_key;
			//Fcur的左边有数据
			if (Fcur->_left)
			{
				Fparent->_right = Fcur->_left;
				delete Fcur;
			}
			//Fcur的左边没有数据
			else
			{
				
				if(Fcur == Fparent->_left)
				{
					Fparent->_left = nullptr;
				}
				else
				{
					Fparent->_right = nullptr;
				}
				delete Fcur;
			}

		}

        第二种情况就不能直接进行交换。因为父节点没有多余的指针指向被删除节点的左右节点。那么在这里的思想是找到一个比被删除的节点的左孩子大,右孩子小。符合条件的是:左子树的最大值,或者右子树的最小值。找到之后交换节点值。但是还是需要注意的是,找到最大值以后,分为两种情况:

        a.最大值的左边为空。

        b.最大值的左边不为空。

那么进行讨论:比如删除的数据是8。

a.最大值的左边为空:

这个条件下可以直接交换删除

b.最大值的左边不为空:

        这种情况下就需要将父节点的右节点(最大值的位置)指向最大值的左节点。

在这段代码中:

			//找到左节点的最大值
			Node* Fparent = cur;
			Node* Fcur = cur->_left;
			while (Fcur ->_right)
			{
				Fparent = Fcur;
				Fcur = Fcur->_right;
			}

        Node* Fparent = cur;的目的是避免这种情况下,空指针解引用的问题:删除的数据是3:

倘若Fparent 为空

        此时Fcur已经为叶子节点(已经为3的左子树的最大值),while循环不会进而以下代码会对Fparent解引用,造成访问空指针的错误。如果将Fparent复制为cur,那么从一开始,Fparent就是Fcur的父节点,既不违反逻辑,也解决了问题。

        因为此时1在父节点的左边,所以综上所述,再删除节点的同时也是需要判断被删除的节点是左节点还是右节点。

示例:

int main()
{
	BSTree<int> tree;

	int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };

	for (auto f : a)
	{
		tree.Insert(f);
	}


	for (auto f : a)
	{
		tree.Erase(f);
		tree.InTraversal();
		cout << endl;
	}

	return 0;
}

结果(中序遍历):

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

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

相关文章

TaskRes: Task Residual for Tuning Vision-Language Models

文章汇总 当前VLMs微调中存在的问题 提示微调的问题 在提示调优中缺乏对先验知识保存的保证(me&#xff1a;即提示微调有可能会丢失预训练模型中的通用知识)。虽然预先训练的文本分支模块(如文本编码器和投影)的权重在提示调优范式中被冻结&#xff0c;但原始的良好学习的分类…

图文深入理解SQL语句的执行过程

List item 本文将深入介绍SQL语句的执行过程。 一.在RDBMS&#xff08;关系型DB&#xff09;中&#xff0c;看似很简单的一条已写入DB内存的SQL语句执行过程却非常复杂&#xff0c;也就是说&#xff0c;你执行了一条诸如select count(*) where id 001 from table_name的非常简…

【Transformers基础入门篇4】基础组件之Model

文章目录 一、Model简介1.1 Transformer1.2 注意力机制1.3 模型类型 二、Model Head2.1 什么是 Model Head2.2 Transformers中的Model Head 三、Model基本使用方法3.0 模型下载-浏览器下载3.1 模型加载与保存3.2 配置加载参数3.3 加载config文件3.2 模型调用3.2.1 带ModelHead的…

【PAM】Linux登录认证限制

PAM&#xff08;Pluggable Authentication Modules&#xff0c;可插拔认证模块&#xff09;是一种灵活的认证框架&#xff0c;用于在 Linux 和其他类 Unix 系统上管理用户的身份验证。PAM 允许系统管理员通过配置不同的认证模块来定制应用程序和服务的认证方式&#xff0c;而不…

软件设计师:01计算机组成与结构

文章目录 一、校验码1.奇偶校验码2.海明码3.循环冗余检验码 二、原码反码补码移码三、浮点数表示法1.浮点数相加时 四、寻址方式五、CPU1.访问速度2.cpu的组成 六、RISC和CISC&#xff08;<font color red>只用记住不同就可以&#xff09;七、冗余技术1.结构冗余2.信息冗…

HyperWorks的实体几何创建与六面体网格剖分

创建和编辑实体几何 在 HyperMesh 有限元前处理环境中&#xff0c;有许多操作是针对“实体几何”的&#xff0c;例如创建六面体网格。在创建实体网格的工作中&#xff0c;我们既可以使用闭合曲面创建实体网格&#xff0c;也可以使用完整的实体几何创建实体网格。与闭合曲面相比…

【rabbitmq-server】安装使用介绍

在 1050a 系统下安装 rabbitmq-server 服务以及基本配置;【注】:改方案用于A版统信服务器操作系统 文章目录 功能概述功能介绍一、安装软件包二、启动服务三、验证四、基本配置功能概述 RabbitMQ 是AMQP的实现,高性能的企业消息的新标准。RabbitMQ服务器是一个强大和可扩展…

截取递增数-第15届蓝桥省赛Scratch中级组真题第6题

[导读]&#xff1a;超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成&#xff0c;后续会不定期解读蓝桥杯真题&#xff0c;这是Scratch蓝桥杯真题解析第191讲。 如果想持续关注Scratch蓝桥真题解读&#xff0c;可以点击《Scratch蓝桥杯历年真题》并订阅合集&#xff0c;…

【c数据结构】OJ练习篇 帮你更深层次理解链表!(相交链表、相交链表、环形链表、环形链表之寻找环形入口点、判断链表是否是回文结构、 随机链表的复制)

目录 一. 相交链表 二. 环形链表 三. 环形链表之寻找环形入口点 四. 判断链表是否是回文结构 五. 随机链表的复制 一. 相交链表 最简单粗暴的思路&#xff0c;遍历两个链表&#xff0c;分别寻找是否有相同的对应的结点。 我们对两个链表的每个对应的节点进行判断比较&…

力扣 209.长度最小的子数组

一、长度最小的子数组 二、解题思路 采用滑动窗口的思路&#xff0c;详细见代码。 三、代码 class Solution {public int minSubArrayLen(int target, int[] nums) {int n nums.length, left 0, right 0, sum 0;int ans n 1; for (right 0; right < n; right ) { …

数通。。。

通信&#xff1a;需要介质才能通信电话离信号塔&#xff08;基站&#xff09;越远&#xff0c;信号越弱。信号在基站之间传递。你离路由器越远&#xff0c;信号越差。一个意思 比如想传一张图片&#xff0c;这张图片就是数据载荷 网关&#xff0c;分割两个网络。路由器可以是网…

Chat2VIS: Generating Data Visualizations via Natural Language

Chat2VIS:通过使用ChatGPT, Codex和GPT-3大型语言模型的自然语言生成数据可视化 梅西大学数学与计算科学学院&#xff0c;新西兰奥克兰 IEEE Access 1 Abstract 数据可视化领域一直致力于设计直接从自然语言文本生成可视化的解决方案。自然语言接口 (NLI) 的研究为这些技术的…

巴黎嫩事件对数据信息安全的影响及必要措施

2024年9月17日&#xff0c;黎巴嫩首都贝鲁特发生了多起小型无线电通信设备爆炸事件&#xff0c;导致伊朗驻黎巴嫩大使受轻伤。这一事件不仅引发了对安全的广泛关注&#xff0c;也对数据信息安全提出了新的挑战。 王工 18913263502 对数据信息安全的影响&#xff1a; 数据泄露风…

MySQL慢查询优化指南

​ 博客主页: 南来_北往 系列专栏&#xff1a;Spring Boot实战 前言 当遇到慢查询问题时&#xff0c;不仅影响服务效率&#xff0c;还可能成为系统瓶颈。作为一位软件工程师&#xff0c;掌握MySQL慢查询优化技巧至关重要。今天&#xff0c;我们就来一场“数据库加速之旅…

Thinkphp(TP)

1.远程命令执行 /index.php?sindex/think\app/invokefunction&functioncall_user_func_array&vars[0]system&vars[1][]whoami 2.远程代码执行 /index.php?sindex/think\app/invokefunction&functioncall_user_func_array&vars[0]phpinfo&vars[1][]…

Java面向对象——内部类(成员内部类、静态内部类、局部内部类、匿名内部类,完整详解附有代码+案例)

文章目录 内部类17.1概述17.2成员内部类17.2.1 获取成员内部类对象17.2.2 成员内部类内存图 17.3静态内部类17.4局部内部类17.5匿名内部类17.5.1概述 内部类 17.1概述 写在一个类里面的类叫内部类,即 在一个类的里面再定义一个类。 如&#xff0c;A类的里面的定义B类&#x…

微信支付商户号注册流程

目录 一、官方指引二、申请规则三、申请流程1.提交资料2.签约协议3.绑定场景 四、微信支付商户登录入口 一、官方指引 https://kf.qq.com/faq/210423UrIRB7210423by6fQn.html 二、申请规则 1、微信支付商家仅面向企业、个体工商户、政府及事业单位、民办非企业、社会团体、基…

java sdk下载,解决下载了java但是编译不了

直接搜Java得到的网站使用不了的 应该只是个功能包或者版本太低用不了 得去oracle公司搜java这个产品去下载

Java语言程序设计基础篇_编程练习题**18.34 (游戏:八皇后问题)

目录 题目&#xff1a;**18.34 (游戏:八皇后问题) 代码示例 代码解析 输出结果 使用文件 题目&#xff1a;**18.34 (游戏:八皇后问题) 八皇后问题是要找到一个解决方案&#xff0c;将一个皇后棋子放到棋盘上的每行中&#xff0c;并且两个皇后棋子之间不能相互攻击。编写个…

Llama 3.1 技术研究报告-2

3.3 基础设施、扩展性和效率 我们描述了⽀持Llama 3 405B⼤规模预训练的硬件和基础设施&#xff0c;并讨论了⼏项优化措施&#xff0c;这些措施提⾼了训练效率。 3.3.1 训练基础设施 Llama 1和2模型在Meta的AI研究超级集群&#xff08;Lee和Sengupta&#xff0c;2022&#x…