C++:AVL树(平衡二叉树)

引言:

AVL树是一种特殊的二叉搜索树,二叉搜索树虽然可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

一棵AVL树具有如下性质:

  • 左右子树都是AVL树
  • 左右子树高度差绝对值不超过1

 一、AVL树结点的定义

 

template<class T>
struct AVLTreeNode
{
AVLTreeNode(const T& data)
  : _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr)
, _data(data), _bf(0)
{}
AVLTreeNode<T>* _pLeft;  // 该节点的左孩子
AVLTreeNode<T>* _pRight;  // 该节点的右孩子
AVLTreeNode<T>* _pParent; // 该节点的双亲
T _data;
int _bf;          // 该节点的平衡因子
};

二、AVL树的插入

 与普通的二叉搜索树不同,AVL树在数据插入后需要维护整棵树的平衡,并且更新结点的平衡因子

AVL树的插入过程可以分为两步: 

  1. 按照二叉搜索树的方式插入新节点
  2. 调整节点的平衡因子
bool Insert(const T& data)
	{
        //如果是空树,直接插入并结束
		if (_pRoot == nullptr)
		{
			_pRoot = new Node(data);
			return true;
		}
        

        //这一块代码的目的是找到插入位置的父亲结点
		Node* parent = nullptr;
		Node* cur = _pRoot;
		while (cur)
		{
			if (data < cur->_data)
			{
				parent = cur;
				cur = cur->_pLeft;
			}
			else if (data > cur->_data)
			{
				parent = cur;
				cur = cur->_pRight;
			}
			else
			{
				return false;
			}
		}



		if (cur == parent->_pLeft)
		{
			cur = new Node(data);
			parent->_pLeft = cur;
		}
		else
		{
			cur = new Node(data);
			parent->_pRight = cur;
		}
		//从parent结点开始向上更新祖先的平衡因子
		while (parent)
		{
			if (parent->_bf == 0)
			{
				break;//插入之后,该结点平衡因子为0说明以该结点为根的子树平衡了
                      //且插入前后高度不变,所以这个结点的祖先的平衡因子不会受到影响,结束
			}
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
                //平衡因子从0变为1或者-1,祖先可能变成2或-2,需要继续向上更新
				cur = parent;
				parent = parent->_pParent;
			}
			else
			{
                //结点的平衡因子变成了2或者-2,需要进行旋转了
                //这里开始需要对AVL树进行旋转,之后再讲
				if (parent->_bf == 2 && cur->_bf == 1)
				{
					RotateL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == -1)
				{
					RotateR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
					RotateRL(parent);
				}
				else
				{
					RotateLR(parent);
				}
				break;
			}
		}
		return true;
	}

三、AVL树的旋转

1.右单旋

 

 

这是一幅抽象图,左子树较高且在左子树的左侧插入 ,`h>=0,此时需要进行右单旋

 我们可以看到这样旋转之后该子树依然是一个二叉搜索树,巧妙的是,根节点的平衡因子被更新  成0了,这说明:在旋转之后不用再对祖先更改平衡因子了

 

void RotateR(Node* pParent)
	{
		Node*subL = pParent->_pLeft;
		Node* subLR = subL->_pRight;
		pParent->_pLeft = subRL;
		if (subLR)
		{
			subLR->_pParent = pParent;
		}
		Node*parentParent = pParent->_pParent;
		subL->_pRight = pParent;
		if (pRoot == pParent)
		{
			pRoot = subL;
			subL->_pParent = nullptr;
		}
		else
		{
			if (parentParent->_pLeft == pParent)
			{
				parentParent->_pLeft = subL;
			}
			else
			{
				parentParent->_pRight = subL;
			}
			subL->_pParent = parentParent;
		}
		pParent->_bf = subL->_bf = 0;
	}

2.左单旋

 左单旋和右单旋类似,只是方向相反,右子树较高且在右子树的右侧插入

void RotateL(Node* pParent)
	{
		Node*subR = pParent->_pRight;
		Node* subRL = subR->_pLeft;
		pParent->_pRight = subRL;
		if (subRL)
		{
			subRL->_pParent = pParent;
		}
		Node* parentParent = pParent->_pParent;
		pParent->_pParent = subR;
		subR->_pLeft = pParent;
		if (parent == pRoot)
		{
			pRoot = subR;
			subR->_pParent = nullptr;
		}
		else
		{
			if (parentParent->_pLeft = pParent)
			{
				parentParent->_pLeft = subR;
			}
			else
			{
				parentParent->_pRight = subR;
			}
		}
		pParent->_bf = subR->_bf = 0;
	}

 

3. 左右双旋

 对于单侧插入,只需要旋转一次。但是对于如下的情况,单次旋转无法解决问题

和单旋不同,我们需要对子树再往下画一层,如图所示

这棵树的左子树较高,并且在左子树的右子树插入,导致单旋无法一次解决

旋转过程 

 step1:

step2: 

 

新插入结点无论插入在subLR的左还是右, 最终subLR平衡因子为0

 

// 左右双旋
	void RotateLR(Node* pParent)
	{
		Node* subL = pParent->_pLeft;
		Node* subLR = pParent->_pRight;
		int bf = subLR->_bf;
		RotateL(subL);
		RotateR(pParent);
		//subLR就是新插入结点
		if (bf == 0)
		{
			pParent->_bf = subL->_bf = subLR->_bf = 0;
		}
		else if (bf == 1)
		{
			pParent->_bf = -1;
			subL->_bf = -1;
			subLR->_bf = 0;
		}
		else if(bf==-1)
		{
			pParent->_bf = 1;
			subL->_bf = 1;
			subLR = 0;
		}
	}

4.右左双旋

 与左右双旋类似

 

//右左双旋
void RotateRL(Node* pParent)
	{
		Node* subR = pParent->_pRight;
		Node* subRL = subR->_pLeft;
		int bf = subRL->_bf;
		RotateR(subR);
		RotateL(pParent);
		//subRL就是新插入结点
		if (bf == 0)
		{
			pParent->_bf = subR->_bf = subRL->_bf = 0;
		}
		else if (bf == 1)
		{
			pParent->_bf = 0;
			subR->_bf = -1;
			subRL->_bf = 0;
		}
		else if(bf==-1)
		{
			pParent->_bf = 1;
			subR->_bf = 1;
			subRL->_bf = 0;
		}
	}

 

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

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

相关文章

基于STM32的烟雾浓度检测报警仿真设计(仿真+程序+讲解视频)

这里写目录标题 &#x1f4d1;1.主要功能&#x1f4d1;2.仿真&#x1f4d1;3. 程序&#x1f4d1;4. 资料清单&下载链接&#x1f4d1;[资料下载链接](https://docs.qq.com/doc/DS0VHTmxmUHBtVGVP) 基于STM32的烟雾浓度检测报警仿真设计(仿真程序讲解&#xff09; 仿真图prot…

iEnglish全国ETP大赛:教育游戏助力英语习得

“seesaw,abacus,sword,feather,frog,lion,mouse……”11月18日,经过3局的激烈较量,“以过客之名队”的胡玲、黄长翔、林家慷率先晋级“玩转英语,用iEnglish”第三届全国ETP大赛的16强,在过去的周末中,还有TIK徘徊者队、不负昭华队、温柔杀戮者队先后晋级。据悉,根据活动规则,在…

杭电oj 2064 汉诺塔III C语言

#include <stdio.h>void main() {int n, i;long long sum[35] { 2,8,26 };for (i 3; i < 35; i)sum[i] 3 * sum[i - 1] 2;while (~scanf_s("%d", &n))printf("%lld\n", sum[n - 1]); }

CentOS 7 使用Fmt库

安装 fmt Git下载地址&#xff1a;https://github.com/fmtlib/fmt 步骤1&#xff1a;首先&#xff0c;你需要下载fmt的源代码。你可以从https://github.com/fmtlib/fmt或者源代码官方网站下载。并上传至/usr/local/source_code/ ​ 步骤2&#xff1a;下载完成后&#xff…

分布式锁3: zk实现分布式锁

一 zk 实现分布式锁 1.1 zk分布式操作命令 1.指令&#xff1a; ls / get /zookeeper create /aa "test" delete /aa set /aa "test1" 2..znode节点类型&#xff1a; 永久节点&#xff1a;create /pa…

Go 语言函数、参数和返回值详解

函数是一组语句&#xff0c;可以在程序中重复使用。函数不会在页面加载时自动执行。函数将通过调用函数来执行。 创建函数 要创建&#xff08;通常称为声明&#xff09;一个函数&#xff0c;请执行以下操作&#xff1a; 使用 func 关键字。指定函数的名称&#xff0c;后跟括…

猫不长肉怎么办?增肥效果好、让猫咪迅速圆润起来的猫罐头分享!

秋冬来临&#xff0c;北方的猫咪有暖气还好过一些&#xff0c;南方的猫咪只能依靠自己的抵抗力来过冬。如果不囤点脂肪&#xff0c;怕冷的小猫咪要怎么过冬啊&#xff1f;有些猫咪无论怎么吃也吃不胖&#xff0c;真的让铲屎官很烦恼。想起我新手养猫的那段日子为了给我家猫咪增…

.Net6使用WebSocket与前端进行通信

1. 创建类WebSocketTest&#xff1a; using System.Net.WebSockets; using System.Text;namespace WebSocket.Demo {public class WebSocketTest{//当前请求实例System.Net.WebSockets.WebSocket socket null;public async Task DoWork(HttpContext ctx){socket await ctx.We…

Gradle常用命令与参数依赖管理和版本决议

一、Gradle 常用命令与参数 本课程全程基于 Gradle8.0 环境 1、Gradle 命令 介绍 gradle 命令之前我们先来了解下 gradle 命令怎么在项目中执行。 1.1、gradlew gradlew 即 Gradle Wrapper&#xff0c;在学习小组的第一课时已经介绍过了这里就不多赘述。提一下执行命令&am…

【tomcat】java.lang.Exception: Socket bind failed: [730048

项目中一些旧工程运行情况处理 问题 1、启动端口占用 2、打印编码乱码 ʮһ&#xfffd;&#xfffd; 13, 2023 9:33:26 &#xfffd;&#xfffd;&#xfffd;&#xfffd; org.apache.coyote.AbstractProtocol init &#xfffd;&#xfffd;&#xfffd;&#xfffd;: Fa…

个人博客项目 - 测试报告

文章目录 一、项目背景二、测试报告功能测试1.编写测试用例2.登录测试3.编写文章测试4.查看文章测试5.删除文章测试7.注销登录测试 自动化测试性能测试1.VUG2.进行场景设计3.生成性能测试报告 总结 本文开始 一、项目背景 通过学习测试相关的知识&#xff0c;动手实践并测试一…

SkyWalking配置报警推送到企业微信

1、先在企业微信群里创建一个机器人&#xff0c;复制webhook的地址&#xff1a; 2、找到SkyWalking部署位置的alarm-settings.yml文件 编辑&#xff0c;在最后面加上此段配置 &#xff01;&#xff01;&#xff01;一定格式要对&#xff0c;不然一直报警报不出来按照网上指导…

强化学习--多维动作状态空间的设计

目录 一、离散动作二、连续动作1、例子12、知乎给出的示例2、github里面的代码 免责声明&#xff1a;以下代码部分来自网络&#xff0c;部分来自ChatGPT&#xff0c;部分来自个人的理解。如有其他观点&#xff0c;欢迎讨论&#xff01; 一、离散动作 注意&#xff1a;本文均以…

2023年【广东省安全员B证第四批(项目负责人)】报名考试及广东省安全员B证第四批(项目负责人)复审考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 广东省安全员B证第四批&#xff08;项目负责人&#xff09;报名考试是安全生产模拟考试一点通总题库中生成的一套广东省安全员B证第四批&#xff08;项目负责人&#xff09;复审考试&#xff0c;安全生产模拟考试一点…

Python开发运维:Celery连接Redis

目录 一、理论 1.Celery 二、实验 1.Windows11安装Redis 2.Python3.8环境中配置Celery 三、问题 1.Celery命令报错 2.执行Celery命令报错 3.Win11启动Celery报ValueErro错误 一、理论 1.Celery (1) 概念 Celery是一个基于python开发的分布式系统&#xff0c;它是简单…

redis 高可用

redis 的性能管理&#xff1a;redis的数据缓存在内存当中。 查看redis性能指标&#xff1a;info memory used_ memory:1800800 redis中数据占用的内存 used_ memory_ rss:5783552 redis向操作系统申请的内存 used_ memory_ peak: 1800800 redis使用内存的峰值。 工作有用 …

持续集成失败:hudson.plugins.git.GitException: Failed to delete workspace

持续集成环境(git gitlab jenkins pipeline maven harbor docker k8s)之前都是ok的&#xff0c;突然就报错了&#xff1a; Cloning the remote Git repository Cloning repository git192.168.117.180:qzcsbj/gift.git ERROR: Failed to clean the workspace jenkins.ut…

2023年【安全生产监管人员】考试题及安全生产监管人员找解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 安全生产监管人员考试题参考答案及安全生产监管人员考试试题解析是安全生产模拟考试一点通题库老师及安全生产监管人员操作证已考过的学员汇总&#xff0c;相对有效帮助安全生产监管人员找解析学员顺利通过考试。 1、…

【问题定位】通过看Mybatis源码解决系统问题

开发需求好好的&#xff0c;运维同事突然发现了一个问题&#xff0c;某个任务的详情页面加载不出来。看日志&#xff0c;系统在进行查询操作的时候抛出空指针异常。感觉是Mybatis内部异常&#xff0c;所以就跟踪源码看下Mybatis运行到哪一步报错的。 DefaultSqlSession#select…

LeetCode算法题解(动态规划)|LeetCode343. 整数拆分、LeetCode96. 不同的二叉搜索树

一、LeetCode343. 整数拆分 题目链接&#xff1a;343. 整数拆分 题目描述&#xff1a; 给定一个正整数 n &#xff0c;将其拆分为 k 个 正整数 的和&#xff08; k > 2 &#xff09;&#xff0c;并使这些整数的乘积最大化。 返回 你可以获得的最大乘积 。 示例 1: 输入…