【数据结构】AVL树的删除(解析有点东西哦)

news2025/1/15 12:55:50

文章目录

  • 前言
  • 一、普通二叉搜索树的删除
    • 1. 删除结点的左右结点都不为空
    • 2. 删除结点的左结点为空,右节点不为空
    • 3. 删除结点的右结点为空,左节点不为空
    • 4. 删除结点的左右结点都不为空
  • 二、AVL树的删除
    • 1. 删除结点,整棵树的高度不变化
      • 1.1 parent的平衡因子在删除结点之前为0
        • 1.1.1 删除结点为parent的左节点
        • 1.1.2 删除结点为parent的右节点
      • 1.2 parent的平衡因子在调整过程中为2
        • 1.2.1 删除结点为parent的左节点
          • 1.2.1.1 parent的右节点的平衡因子为0
          • 1.2.1.2 parent的右节点的平衡因子为1
        • 1.2.2 删除结点为parent的右节点
          • 1.2.2.1 parent的左节点的平衡因子为0
          • 1.2.2.2parent的左节点的平衡因子为 -1
    • 2. 删除结点,整棵树的高度变化
      • 2.1 删除结点为parent的左节点
        • 2.1.1 parent的平衡因子在删除结点之前为 -1
        • 2.1.2 parent的平衡因子为2且parent的右结点的平衡因子为-1
          • 2.1.2.1 PRleft的平衡因子为1
          • 2.1.2.2 PRleft的平衡因子为-1
      • 2.2 删除结点为parent的右节点
        • 2.1.1 parent的平衡因子在删除结点之后为 0
        • 2.1.2 parent的平衡因子为-2且parent的左节点的平衡因子为1
          • 2.1.2.1 PLright的平衡因子为 1
          • 2.1.2.2 PLright的平衡因子为-1
  • 实现代码
  • 详细图解(建议收藏)
  • 总结

前言

  由于AVL树也是二叉搜索树,为了降低一点难度,我们就基于普通二叉搜索树的删除的铺垫,再进行相关的操作。

一、普通二叉搜索树的删除

第一步:先找到要删除的结点,没有返回false。
情况分析:

1. 删除结点的左右结点都不为空

  • 父节点为根节点。

例:
在这里插入图片描述

删除1,但父节点也是1,直接将根节点置为空即可。

  • 父节点不为根节点。

例:
在这里插入图片描述

删除5结点,将其父节点的指向,指向空即可。

2. 删除结点的左结点为空,右节点不为空

  • 父节点为根节点

在这里插入图片描述

删除1,这里只需要将2改为根节点。

  • 父节点不为根节点。

在这里插入图片描述

删除5结点,将其父节点,指向5的右结点即可,再删除6即可。

3. 删除结点的右结点为空,左节点不为空

  • 父节点为根节点

例:
在这里插入图片描述

直接将0置为根节点即可。

  • 父节点不为根节点。

例:
在这里插入图片描述

将0的父节点的左孩子的指向改为-1,再释放0结点。

4. 删除结点的左右结点都不为空

 这里我们就采用置换法的找左子树的最大节点进行值交换即可。

如果删除结点为根节点,由于我们置换的是值,再对交换后的结点进行讨论:

  1. 交换后的结点,必然不为根节点。
  2. 交换后的结点的右结点必然为空。
  3. 交换后的结点的左节点可能不为空。
  4. 交换后的结点,其父节点的左边可能为交换后的结点,父节点的右边也坑为交换后的结点,就此点展开讨论。

说明:交换后的结点,原先为左子树的最大节点,我们用left_most_node进行表示。

  • 父节点的左节点为left_most_node

例:
在这里插入图片描述

待删除结点为1,left_most_node为0,交换后1变0, 0变1,然后父节点的左节点指向left_most_node的左结点即可。

  • 父节点的右节点为left_most_node

例:
在这里插入图片描述

待删除结点为1,left_most_node为-1,交换后1变-1, -1变1,然后父节点的右节点指向left_most_node的左结点即可。

 如果连普通的二叉搜索树的相关知识有所缺陷,可以看一下我之前写的这篇文章——二叉搜索树。本文的普通二叉树的删除,也是源自于这篇文章。

二、AVL树的删除

 说明一点,在正式讲AVL树之前,最好先把AVL树的插入及四种旋转了解清楚,因为删除说白了就是在这个基础上分类讨论的结果。

如果不清楚, 请看此篇文章:AVL树的插入与验证

OK,铺垫完成,正文开始。

删除时,再基于上面删除的四种情况外,我们还要考虑两大类情况。即整棵树的高度变化(降一) + 高度不变化。高度变化会影响上面的树的平衡因子,因此需要向上更新。

1. 删除结点,整棵树的高度不变化

两种情况,parent的平衡因子为0或者为2

1.1 parent的平衡因子在删除结点之前为0

1.1.1 删除结点为parent的左节点

在这里插入图片描述

整棵树的高度未发生变化,调整结束。

1.1.2 删除结点为parent的右节点

情况与左节点类似,上面理解,这块可以不用看。

在这里插入图片描述

整棵树的高度未发生变化,调整结束。

1.2 parent的平衡因子在调整过程中为2

1.2.1 删除结点为parent的左节点

1.2.1.1 parent的右节点的平衡因子为0

在这里插入图片描述

进行左旋,更新parent_right和parent的平衡因子分别为 -1 与 1,整棵树的高度不发生变化,调整结束。

1.2.1.2 parent的右节点的平衡因子为1

在这里插入图片描述

左旋之后,parent与parent_right的平衡因子变为0,结束调整。

1.2.2 删除结点为parent的右节点

情况与左节点类似,上面理解,这块可以不用看。

1.2.2.1 parent的左节点的平衡因子为0

在这里插入图片描述

进行右旋,更新parent_left与parent的平衡因子分别为1与-1,调整结束。

1.2.2.2parent的左节点的平衡因子为 -1

在这里插入图片描述

进行右旋,更新parent_left与parent的平衡因子0,调整结束。

2. 删除结点,整棵树的高度变化

2.1 删除结点为parent的左节点

2.1.1 parent的平衡因子在删除结点之前为 -1

在这里插入图片描述

调整parent的平衡因子为0,继续向上进行调整。

2.1.2 parent的平衡因子为2且parent的右结点的平衡因子为-1

为了便于描述。这里把parent右节点记作Pright,Pright的左节点记作PRleft

2.1.2.1 PRleft的平衡因子为1

在这里插入图片描述

进行右左双旋,且把parent的平衡因子调整为-1,PRleft的平衡因子调整为0,Pright的平衡因子调整为0。

2.1.2.2 PRleft的平衡因子为-1

在这里插入图片描述

进行右左双旋,且把parent的平衡因子调整为0,PRleft的平衡因子调整为0,Pright的平衡因子调整为1。

2.2 删除结点为parent的右节点

2.1.1 parent的平衡因子在删除结点之后为 0

在这里插入图片描述

parent的平衡因子改为0,继续向上调整。

2.1.2 parent的平衡因子为-2且parent的左节点的平衡因子为1

为了便于描述。这里把parent左节点记作Pleft,Pleft的右节点记作PLright

2.1.2.1 PLright的平衡因子为 1

在这里插入图片描述

进行左右双旋,调整PLright的平衡因子为0,parent的平衡因子为0,Pleft的平衡因子为-1。继续向上进行调整。

2.1.2.2 PLright的平衡因子为-1

在这里插入图片描述

进行左右双旋,调整PLright的平衡因子为0,parent的平衡因子为0,Pleft的平衡因子为-1。继续向上进行调整。

实现代码

  • 每个人的写出来的代码可能不一样,但思路还是一致的,因此重点不在代码,而在分析思路上,这里只是贴出来方便大家对照。
		Node* find(const Key &val)
		{
			Node* cur = _root;
			while (cur)
			{
				if (cur->_key > val)
				{
					//左子树中查找
					cur = cur->_left;
				}
				else if (cur->_key < val)
				{
					//右子树中查找
					cur = cur->_right;

				}
				else
				{
					//找到了
					return cur;
				}
			}
			return nullptr;
		}
		//为了避免删除时的代码过于冗余,将重复利用的代码写成一个函数较好。
		void UpDateBFactor(Node* parent, Node* cur)
		{
			while (parent)
			{

				if (parent->_left == cur)
				{
					parent->_bf++;
				}
				else
				{
					parent->_bf--;
				}

				//判断平衡因子
				if (parent->_bf == 1 || parent->_bf == -1)
				{
					break;
				}
				else if (parent->_bf == 0)
				{
					cur = parent;
					parent = parent->_parent;
				}
				else if (parent->_bf == 2)
				{
					if (parent->_right->_bf == 0)
					{
						//进行左单旋
						Node* parent_right = parent->_right;
						RotateL(parent);
						parent->_bf = 1;
						parent_right->_bf = -1;
						//高度不变
						break;
					}
					else if(parent->_right->_bf == 1)
					{
						//左单旋

						RotateL(parent);
						//更新平衡因子
					}
					else
					{
						//进行右左双旋
						RotateRL(parent);
					}
					cur = parent->_parent;
					parent = cur->_parent;
				}
				else if (parent->_bf == -2)
				{
					//这还有一种特殊情况
					if (parent->_left->_bf == 0)
					{
						Node* parent_left = parent->_left;
						RotateR(parent);
						parent->_bf = -1;
						parent_left->_bf = 1;
						//此时高度不变
						break;
					}
					else if (parent->_left->_bf == -1)
					{
						//右单旋
						RotateR(parent);
						//高度降低1
					}
					else
					{
						//左右双旋
						RotateLR(parent);
						//高度降低1
					}
					cur = parent->_parent;
					parent = cur->_parent;
				}
				else
				{
					cout << parent->_key << ":";
					perror("平衡因子有误");
					exit(-1);
				}
			}
		}
		//AVL树结点的删除
		bool erase(const Key& val)
		{
			Node* cur = find(val);
			//对cur进行判空
			if (cur == nullptr)
			{
				//说明没找到。
				return false;
			}
			else
			{
				Node* parent = cur->_parent;
				Node* delnode = cur;
				Node* delparent = cur->_parent;

				//更新平衡因子,然后判断旋转与删除。
				//左加加,右减减
				//分析四种情况
				//1.cur的left和right都为空。
				//2.cur的left为空,right不为空。
				//3.cur的right为空,left不为空。
				//4.cur的right与left都不为空。
				//在此基础上还要判断cur是否为根节点。

				//1.cur的left和right都为空。
				if (cur->_left == nullptr && cur->_right == nullptr)
				{
					//更新平衡因子
					//判断cur是否为根节点
					if (cur == _root)
					{
						_root = nullptr;
					}
					else
					{
						
						UpDateBFactor(parent, cur);
						//将父节点的指向清空
						parent = delnode->_parent;
						if (parent->_left == delnode)
						{
							parent->_left = nullptr;
						}
						else
						{
							parent->_right = nullptr;
						}
					}
					//删除结点。
					delete delnode;
				}
				//左不为空,并且右为空
				else if (cur->_left && cur->_right == nullptr)
				{
					if (cur == _root)
					{
						//更改根节点
						_root = cur->_left;
						//改变根节点的父节点的指向,因为平衡因子的绝对值必然小于等于1,
						//因此其左节点只有一个结点。
						//因此只需更改父节点的指向即可。
						_root->_parent = nullptr;
						//无需更新平衡因子
						delete delnode;
					}
					else
					{
						Node* cur_left = cur->_left;
						//更新平衡因子
						UpDateBFactor(parent, cur);
						//更新cur_left到cur的位置
						cur_left->_parent = delnode->_parent;
						//更新其父节点的指向
						if (parent->_left == delnode)
						{
							parent->_left = cur_left;
						}
						else
						{
							parent->_right = cur_left;
						}
						//删除delnode
						delete delnode;
					}
				}
				else if (cur->_left == nullptr && cur->_right)
				{
					if (cur == _root)
					{
						//更新根节点
						_root = cur->_right;
						//说明:为了保证树为AVL树,cur->_right必然无左右孩子
						//更新根节点的信息
						_root->_parent = nullptr;
						//释放delnode
						delete delnode;
					}
					else
					{
						Node* parent = cur->_parent;
						Node* cur_right = cur->_right;
						//更新平衡因子
						UpDateBFactor(parent, cur);
						//首先改变cur_right的父节点的指向
						cur_right->_parent = delparent;
						//其次改变parent其孩子的指向
						if (delparent->_left == delnode)
						{
							delparent->_left = cur_right;
						}
						else
						{
							delparent->_right = cur_right;
						}
						//最后删除cur
						delete delnode;
					}
				}
				//4.cur的right与left都不为空。
				else
				{
					//可以找左子树的最大节点,也可以找右子树的最小节点。
					//进行交换,然后更新平衡因子
					Node* mostleft = cur->_left;
					while (mostleft && mostleft->_right)
					{
						mostleft = mostleft->_right;
					}
					Node* curr = mostleft->_left;

					//直接进行赋值。
					cur->_key = mostleft->_key;
					cur->_val = mostleft->_val;
					Node* parent = mostleft->_parent;

					UpDateBFactor(parent,mostleft);

					if (mostleft == cur->_left)
					{
						if (curr != nullptr)
						{
							//改变cur与curr的指向。
							curr->_parent = cur;
							cur->_left = curr;
						}
						cur->_left = curr;
					}
					else
					{
						Node* parent = mostleft->_parent;
						parent->_right = curr;
						if (curr != nullptr)
						{
							//改变curr的父节点的指向
							curr->_parent = parent;
						}
					}
					//释放mostleft
					delete mostleft;
					//更新平衡因子
				}
					return true;
				}
			}

详细图解(建议收藏)

总结

删除的例子我们已经举完了,那么这里我们再总结一下:

  1. 调整完后,整棵树的高度变化(降低一个高度),会影响上面树的高度,需要向上调整。
  2. 调整完后,整棵树的高度不变,不会影响上面树的高度,无需调整。
  3. 最坏调整到根节点结束。

具体情况的分析,我等价替换一下:

  • 以删除的结点为左节点举例。
  1. parent的平衡因子为2,且其右节点的平衡因子为-1,等价于插入的右左双旋。
  2. parent的平衡因子为2,且其右节点的平衡因子为1, 等价于插入的左双旋。

说明: 这里的等价是包括平衡因子的调整的!

  • 核心情况
  1. parent的平衡因子为2且parent_right的平衡因子为0.
    在这里插入图片描述

这种情况是需要我们在插入的基础上,进行左旋之后,手动调平衡因子的。

  1. 在更新平衡因子时,插入是左减减,右加加。删除是左加加,右减减。
  2. 判断是否需要向上更新时,插入是1或者-1向上调整,删除是0向上调整。

 其实分析了那么多情况,其实也就这几点需要我们真正注意的,剩余的用插入剩下的轮子套用即可。

最后如果觉得文章不错的话,点个赞鼓励一下吧!

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

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

相关文章

RISV-V架构的寄存器介绍

1、RISC-V的通用寄存器 &#xff08;1&#xff09;在编写汇编代码时&#xff0c;使用寄存器的ABI名字&#xff0c;一般不直接使用寄存器的编号&#xff1b; &#xff08;2&#xff09;x0-x31是用来做整形运算的寄存器&#xff0c;f0-f31是用来做浮点数运算的寄存器&#xff1b;…

傅里叶变换应用 (01/2):频域和相位

一、说明 我努力理解傅里叶变换&#xff0c;直到我将这个概念映射到现实世界的直觉上。这是一系列技术性越来越强的解释中的第一篇文章。我希望直觉也能帮助你&#xff01; 二、傅里叶变换中频域简介 声音是一种机械波&#xff0c;是空气中的振动或其他介质。音符对应于波的频率…

【LeetCode75】第五十七题 电话号码的字母组合

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 给我们按下的按键&#xff0c;让我们返回对应按键可能产生的所有可能。 这是一道很经典的递归题&#xff0c;我们首先先拿一个数组把每个…

day45:C++ day5,运算符重载剩余部分、静态成员、继承

#include <iostream> #include <cstring> #define pi 3.14 using namespace std;class Shape { protected:double round;double area; public://无参构造Shape():round(40),area(100){cout<<"Shape::无参构造函数&#xff0c;默认周长为40&#xff0c;面…

C语言入门Day_21 函数的使用

目录 前言&#xff1a; 1.变量作用域 2.代码执行顺序 3.易错点 4.思维导图 前言&#xff1a; 我们是先定义函数&#xff0c;再调用函数。完成了函数的定义以后&#xff0c;我们就可以开始调用函数了&#xff0c;让我们来回顾一下&#xff1a; 调用函数分为两部分&#…

1131. 绝对值表达式的最大值

1131. 绝对值表达式的最大值 原题链接&#xff1a;完成情况&#xff1a;解题思路&#xff1a;求方向一次遍历两度统计 参考代码&#xff1a;求方向一次遍历两度统计 原题链接&#xff1a; 1131. 绝对值表达式的最大值 https://leetcode.cn/problems/maximum-of-absolute-val…

网络安全深入学习第二课——热门框架漏洞(RCE—Thinkphp5.0.23 代码执行)

文章目录 一、什么是框架&#xff1f;二、导致框架漏洞原因二、使用步骤三、ThinkPHP介绍四、Thinkphp框架特征五、Thinkphp5.0.23 远程代码执行1、漏洞影响范围2、漏洞成因 六、POC数据包Windows下的Linux下的 七、漏洞手工复现1、先Burp抓包&#xff0c;把抓到的请求包发送到…

【AI语言大模型】文心一言功能使用介绍

一、前言 文心一言是一个知识增强的大语言模型,基于飞桨深度学习平台和文心知识增强大模型,持续从海量数据和大规模知识中融合学习具备知识增强、检索增强和对话增强的技术特色。 最近收到百度旗下产品【文心一言】的产品,抱着试一试的心态体验了一下,整体感觉:还行! 二…

自动化测试API【软件测试】

自动化测试 selenium 1. 为什么使用selenium&#xff1f; 开源免费支持多浏览器支持多系统支持多语言编程提供了丰富的web自动化测试API 2. API 查找页面元素 find Element() find Elements() 元素定位的方式 xpath selector 通常情况下&#xff0c;不需要手动来编写xpath…

Learn Prompt-经验法则

还记得我们在“基础用法”当中提到的三个经验法则吗&#xff1f; 尝试提示的多种表述以获得最佳结果使用清晰简短的提示&#xff0c;避免不必要的词语减少不精确的描述 现在经过了几页的学习&#xff0c;我认为是时候引入一些新的原则了。 3. 一个话题对应一个chat​ ChatG…

Kafka开篇

前言 从本篇开始对个人Kafka学习做一个总结, 目标有这么几个。 从概念架构角度, 对消息中间件形成概要认知;从使用角度, 掌握其常见用法;从性能角度, 探究其高性能实现机制; 消息中间件的用途 从消息生产和消费的角度, 平衡消费者和消费者的速率差。基于该点可以做到削峰填…

白炽灯对新生儿视力有影响吗?推荐专业的儿童台灯

大家都知道婴儿还在成长发育的重要阶段&#xff0c;身体各方面都是比较脆弱的&#xff0c;对外界事务的感知也很敏感&#xff0c;一点点的刺激都会影响的婴儿。而白炽灯是否适合婴儿使用这个问题&#xff0c;我的建议是尽量不要用白炽灯。 因为白炽灯光线不是很柔和&#xff0c…

周易算卦流程c++实现

代码 #include<iostream> using namespace std; #include<vector> #include<cstdlib> #include<ctime> #include<Windows.h>int huaYiXiangLiang(int all, int& left) {Sleep(3000);srand(time(0));left rand() % all 1;while (true) {if…

许可分析 license分析 第十二章

许可分析是指对软件许可证进行详细的分析和评估&#xff0c;以了解组织内部对软件许可的需求和使用情况。通过许可分析&#xff0c;可以帮助组织更好地管理和优化软件许可证的使用。以下是一些可能的许可分析方法和步骤&#xff1a; 软件许可证的分配和使用权限&#xff1a;制定…

01Spring的Ioc思想和依赖注入手段(DI)

传统方式创建对象的缺陷 连接MySQL实现登录功能 控制层UserController public class UserController {//多态,半面向接口编程private UserService userService new UserServiceImpl();public void login(){String username "admin";String password "1234…

基于 Zookeeper 实现分布式锁

文章目录 前言声明前置知识分布式锁设计原则Zookeeper 分布式锁实现Curator框架实现分布式锁总结 前言 在分布式系统中&#xff0c;确保数据的一致性和避免冲突是一个核心问题&#xff0c;通常我们通过分布式锁来解决&#xff0c;分布式锁本质是一种同步机制&#xff0c;用于控…

【AI】机器学习——支持向量机(线性模型)

支持向量机是一种二分类算法&#xff0c;通过在高维空间中构建超平面实现对样本的分类 文章目录 5.1 SVM概述5.1.1 分类 5.2 线性可分SVM5.2.1 线性可分SVM基本思想5.2.2 策略函数间隔几何间隔硬间隔最大化 5.2.3 原始算法支持向量 5.2.4 对偶形式算法1. 构造并求解对偶问题2. …

【小沐学CAD】嵌入式UI开发工具:GL Studio

文章目录 1、简介2、软件功能3、应用行业3.1 航空3.2 汽车3.3 防御3.4 工业3.5 电力与能源3.6 医疗3.7 空间3.8 科技 结语 1、简介 https://disti.com/gl-studio/ DiSTI 是 HMI 软件、虚拟驾驶舱、仪表、信息娱乐、集群显示器和嵌入式 UI 解决方案的领先提供商。 而它的GL Stu…

kubernetes部署(kubeadmin)

文章目录 1.环境准备2. 安装dokcer3.部署cri-docker4.各个节点安装kubeadm等5.整合kubelet和cri-dockerd配置cri-dockerd配置kubelet 6.初始化集群 1.环境准备 环境和软件版本 OS : ubuntu 20.04 container runtime: docker CE 20.10.22 kubernetes 1.24.17 CRI&#xff1a;cr…

atoi函数

介绍&#xff1a; 头文件: <stdlib.h> 此函数的功能是将数字字符的字符串转化为字面上的整型返回。 例如&#xff1a; char arr1[] "-12"; char arr2[] "12"; char arr3[] " -12"; char arr4[] "-12a";使用atoi 我们…