一篇文章带你读懂AVL树

news2024/11/24 2:35:09

目录

AVL树节点的定义

AVL树的插入 

 AVL树的旋转

1. 新节点插入较高左子树的左侧---左左:右单旋

2.新节点插入较高右子树的右侧---右右:左单旋

3. 新节点插入较高左子树的右侧---左右:先左单旋再右单旋

4. 新节点插入较高右子树的左侧---右左:先右单旋再左单旋

AVL树的验证


AVL树是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。

性质:

  • 它的左右子树都是AVL树
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)

如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 O(log2 n)--以2为底n的对数,搜索时间复杂度O(log2 n)。 

AVL树节点的定义

 template<class K,class V>
struct AVLTreeNode
{
    AVLTreeNode<K, V>* _left;        // 该节点的左孩子
    AVLTreeNode<K, V>* _right;     // 该节点的右孩子
    AVLTreeNode<K, V>* _parent;  // 该节点的双亲
    pair<K, V> _kv;

    int _bf;                                      //balance factor  右子树高度-左子树高度

    AVLTreeNode(const pair<K, V>& kv)

    :_left(nullptr)

    , _right(nullptr)

    , _parent(nullptr)

    , _bf(0)

    , _kv(kv)

    {}

};

AVL树的插入 

AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么 AVL树的插入过程可以分为两步:

1. 按照二叉搜索树的方式插入新节点

2. 调整节点的平衡因子

更新平衡因子的五种情况:

1.cur==parent->left parent->bf--
 2.cur==parent->right parent->bf++
 3.更新以后,parent->bf==0,更新结束。(说明更新前parent->bf是 1 或者 -1,现在变成0,矮的那一边被填上了,parent所在子树高度不变)
 4.更新以后,parent->bf==1/-1,继续向上更新。(说明更新前parent->bf是0,现在变成 1 或者 -1,有一边子树变高了,parent所在子树高度改变,需要继续向上更新)
 5.更新以后,parent->bf==2/-2,parent所在子树已经不平衡,需要旋转处理。 

bool Insert(const pair<K,V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}

		//插入节点
		cur = new Node(kv);
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}

		//控制平衡
		while (parent)
		{
			// 更新双亲的平衡因子
			if (cur == parent->_left)
				parent->_bf--;
			else
				parent->_bf++;

			// 更新后检测双亲的平衡因子
			if (parent->_bf == 0)
			{
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				// 插入前双亲的平衡因子是0,插入后双亲的平衡因为为1 或者 -1 ,说明以双亲为根的二叉树的高度增加了一层,因此需要继续向上调整
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				// 双亲的平衡因子为正负2,违反了AVL树的平衡性,需要对以parent为根的树进行旋转处理
				
				if (parent->_bf == -2 || cur->_bf == -1)//右单旋
				{
					RotateR(parent);
				}
				else if (parent->_bf == 2 || cur->_bf == 1)//左单旋
				{
					RotateL(parent);
				}
				else if (parent->_bf == -2 || cur->_bf == 1)
				{
					RotateLR(parent);
				}
				else if (parent->_bf == 2 || cur->_bf == -1)
				{
					RotateRL(parent);
				}
				break;
			}
			else
			{
				//插入更新平衡因子之前,树中平衡因子就有问题了
				assert(false);
			}
		}
		return true;
	}

 AVL树的旋转

如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构, 使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:

1. 新节点插入较高左子树的左侧---左左:右单旋

 上图在插入前,AVL树是平衡的,新节点插入到30的左子树(注意:此处不是左孩子)中,30左子树增加了一层,导致以60为根的二叉树不平衡,要让60平衡,只能将60左子树的高度减少一层,右子树增加一层,  即将左子树往上提,这样60转下来,因为60比30大,只能将其放在30的右子树,而如果30有右子树,右子树根的值一定大于30,小于60,只能将其放在60的左子树,旋转完成后,更新节点的平衡因子即可。在旋转过程中,有以下几种情况需要考虑:  

1. 30节点的右孩子可能存在,也可能不存在  。

2. 60可能是根节点,也可能是子树。如果是根节点,旋转完成后,要更新根节点;如果是子树,可能是某个节点的左子树,也可能是右子树 。

图解:

void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
         // 如果左孩子的右孩子存在,更新双亲
		if (subLR)
			subLR->_parent = parent;

        因为更新的可能是棵子树,因此在更新其双亲前必须先保存该节点的双亲
		Node* parentParent = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;
		
        //如果旋转的是根节点,更新指向根节点的指针
		if (parent == _root)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
         // 如果更新的是子树,可能是其双亲的左子树,也可能是右子树
			if (parentParent->_left == parent)
				parentParent->_left = subL;
			else
				parentParent->_right = subL;

			subL->_parent = parentParent;
		}

		subL->_bf = parent->_bf = 0;
	}

2.新节点插入较高右子树的右侧---右右:左单旋

void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		Node* parentParent = parent->_parent;

		subR->_left = parent;
		parent->_parent = subR;

		if (parent == _root)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (parentParent->_left == parent)
				parentParent->_left = subR;
			else
				parentParent->_right = subR;

			subR->_parent = parentParent;
		}

		subR->_bf = parent->_bf = 0;
	}

3. 新节点插入较高左子树的右侧---左右:先左单旋再右单旋

将双旋变成单旋后再旋转,即:先对30进行左单旋,然后再对90进行右单旋,旋转完成后再 考虑平衡因子的更新。 

// 旋转之前,60的平衡因子可能是-1/0/1,旋转完成之后,根据情况对其他节点的平衡因子进行调整
void _RotateLR(PNode pParent)
{
 PNode pSubL = pParent->_pLeft;
 PNode pSubLR = pSubL->_pRight;
    
    // 旋转之前,保存pSubLR的平衡因子,旋转完成之后,需要根据该平衡因子来调整其他节
点的平衡因子
 int bf = pSubLR->_bf;
    
    // 先对30进行左单旋
 _RotateL(pParent->_pLeft);
    
    // 再对90进行右单旋
 _RotateR(pParent);
 if(1 == bf)
     pSubL->_bf = -1;
 else if(-1 == bf)
     pParent->_bf = 1;
}

4. 新节点插入较高右子树的左侧---右左:先右单旋再左单旋

 

假如以parent为根的子树不平衡,即parent的平衡因子为2或者-2,分以下情况考虑:

1. parent的平衡因子为2,说明parent的右子树高,设parent的右子树的根为pSubR 当pSubR的平衡因子为1时,执行左单旋 当pSubR的平衡因子为-1时,执行右左双旋

2. parent的平衡因子为-2,说明parent的左子树高,设parent的左子树的根为pSubL 当pSubL的平衡因子为-1是,执行右单旋 当pSubL的平衡因子为1时,执行左右双旋

旋转完成后,原parent为根的子树个高度降低,已经平衡,不需要再向上更新。

void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;
		RotateR(parent->right);
		RotateL(parent);

		if (bf == 1)
		{
			parent->_bf = -1;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else if (bf == -1)
		{
			parent->_bf = 0;
			subR->_bf = 1;
			subRL->_bf = 0;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

AVL树的验证

AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:

1. 验证其为二叉搜索树

        如果中序遍历可得到一个有序的序列,就说明为二叉搜索树

2. 验证其为平衡树

  • 每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)
  • 节点的平衡因子是否计算正确

 

int _Height(PNode pRoot);
bool _IsBalanceTree(PNode pRoot)
{
 // 空树也是AVL树
 if (nullptr == pRoot) 
    return true;
    
 // 计算pRoot节点的平衡因子:即pRoot左右子树的高度差
 int leftHeight = _Height(pRoot->_pLeft);
 int rightHeight = _Height(pRoot->_pRight);
 int diff = rightHeight - leftHeight;

// 如果计算出的平衡因子与pRoot的平衡因子不相等,或者
 // pRoot平衡因子的绝对值超过1,则一定不是AVL树
 if (diff != pRoot->_bf || (diff > 1 || diff < -1))
     return false;
 // pRoot的左和右如果都是AVL树,则该树一定是AVL树
 return _IsBalanceTree(pRoot->_pLeft) && _IsBalanceTree(pRoot-
>_pRight);
 }

AVL树的性能

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这 样可以保证查询时高效的时间复杂度,即log2 (N)。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时, 有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。

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

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

相关文章

人工智能自然语言处理—PageRank算法和TextRank算法详解

人工智能自然语言处理—PageRank算法和TextRank算法详解 一、PageRank算法 PageRank算法最初被用作互联网页面重要性的计算方法。它由佩奇和布林于1996年提出&#xff0c;并被用于谷歌搜索引擎的页面排名。事实上&#xff0c;PageRank可以在任何有向图上定义&#xff0c;然后…

公司企业如何设计微信小程序?

​很多公司企业在制作小程序的时候都会考虑一个事情&#xff0c;就是如何设计微信小程序。有些公司企业希望把小程序设计得非常炫酷、抓人眼球。那么问题是&#xff1a;公司企业微信小程序的设计是否做得越酷炫、越抓人眼球就越好呢&#xff1f; 答案&#xff1a;非也&#xf…

基于SIFT的图像Matlab拼接教程

前言图像拼接技术&#xff0c;将普通图像或视频图像进行无缝拼接&#xff0c;得到超宽视角甚至360度的全景图&#xff0c;这样就可以用普通数码相机实现场面宏大的景物拍摄。利用计算机进行匹配&#xff0c;将多幅具有重叠关系的图像拼合成为一幅具有更大视野范围的图像&#x…

(一)Spring源码解析:容器的基本实现

一、Spring的整体架构 Spring的整体架构图如下所示&#xff1a; 二、容器的基本实现 2.1> 核心类介绍 2.1.1> DefaultListableBeanFactory DefaultListableBeanFactory是整个bean加载的核心部分&#xff0c;是Spring注册及加载bean的默认实现。 XmlBeanFactory集成自…

【FLASH存储器系列十四】固态硬盘结构和FTL初探

固态硬盘是一种典型的nand flash产品应用。与传统硬盘相化&#xff0c;固态硬盘内部没有移动的机械磁头&#xff0c;而是由固态电子存储芯片&#xff08;闪存芯片&#xff09;阵列级联组成&#xff0c;下图给出了固态硬盘的内部组成。现阶段&#xff0c;几乎所有基于闪存的固态…

ASP.NET Core+Element+SQL Server开发校园图书管理系统(三)

随着技术的进步&#xff0c;跨平台开发已经成为了标配&#xff0c;在此大背景下&#xff0c;ASP.NET Core也应运而生。本文主要基于ASP.NET CoreElementSql Server开发一个校园图书管理系统为例&#xff0c;简述基于MVC三层架构开发的常见知识点&#xff0c;前两篇文章简单介绍…

Nvidia深度学习环境安装

深度学习大型模型训练和部署&#xff0c;需要使用GPU&#xff0c;使用Pytorch、Tensorflow等深度学习框架之前需要安装驱动环境,本文系统环境&#xff1a;ubuntu22.04系统&#xff0c;四张3090显卡安装显卡驱动下载&#xff1a;选择显卡类型&#xff0c;下载驱动驱动下载路径&a…

Wireshark解析协议不匹配

Wireshark解析协议不匹配 1、问题 现有TLS/SSL over TCP的客户端、服务端相互通信&#xff0c;其中&#xff0c;服务端监听TCP端口6000。 使用tcpdump抓包6000端口&#xff0c;生成pcap文件6000.pcap&#xff1a; 使用Wireshark打开6000.pcap&#xff0c;显示如下&#xff1…

Hive(番外):Hive可视化工具IntelliJ IDEA

1 Hive CLI、Beeline CLI Hive自带的命令行客户端 优点&#xff1a;不需要额外安装 缺点&#xff1a;编写SQL环境恶劣&#xff0c;无有效提示&#xff0c;无语法高亮&#xff0c;误操作几率高 2 文本编辑器 Sublime、Emacs 、EditPlus、UltraEdit、Visual Studio Code等 有…

基于Seam Carving实现图像的重定位 附完整代码

相比于算法目标的复杂&#xff0c;算法步骤却异常的简单&#xff0c;下面具体介绍利用 SeamCarving 算法进行图像剪裁的步骤&#xff1a;1.计算图像中每个像素的“重要程度”&#xff08;能量&#xff09;&#xff0c;生成能量图。在绝大多数情况下&#xff0c;我们可以做出如下…

【string 类的使用方法(总结)】

1. 为什么学习string类&#xff1f; C语言中&#xff0c;字符串是以\0结尾的一些字符的集合&#xff0c;为了操作方便&#xff0c;C标准库中提供了一些str系列的库函数&#xff0c;但是这些库函数与字符串是分离开的&#xff0c;不太符合OOP的思想&#xff0c;而且底层空间需要…

采用NVIDIA Jetson Orin NX 系统的视觉边缘计算机

边缘计算机采用NVIDIA Jetson Orin NX模块化系统和高带宽图像采集卡&#xff0c;用于实时图像采集计算和人工智能处理。虹科的合作伙伴Gidel是一家专注于高速图像采集和处理的以色列科技公司&#xff0c;今天宣布新的NVIDIA Jetson Orin NX™ 16GB模块化系统(SoM)将被添加到Gid…

SpringSecurity配置及使用

Spring Security 是针对Spring项目的安全框架&#xff0c;也是Spring Boot底层安全模块默认的技术选型&#xff0c;他可以实现强大的Web安全控制&#xff0c;对于安全控制&#xff0c;我们仅需要引入spring-boot-starter-security 模块&#xff0c;进行少量的配置&#xff0c;即…

什么是渲染农场,渲染农场一般怎么收费?

对于用3D软件创作效果图或影视动画的艺术家们来说&#xff0c;应该对渲染农场并不陌生&#xff0c;但是对于初入CG行业的人来说&#xff0c;看到网上很多人说渲染农场&#xff0c;肯定会疑惑&#xff0c;什么是渲染农场&#xff1f;渲染农场也叫“分布式并行集群计算系统”&…

【6】【vue3+elementplus+springboot】 管理系统 【前后端实践】

第一部分&#xff1a; elementplus官网&#xff1a;一个 Vue 3 UI 框架 | Element Plus (element-plus.org) 1、安装elementplus npm install element-plus --save查看package.json中存在依赖表示成功安装 2、引入elementplus import ElementPlus from element-plus import …

论文解读 - 城市自动驾驶车辆运动规划与控制技术综述 (第5部分,完结篇)

文章目录&#x1f697; V. Vehicle Control&#xff08;车辆控制&#xff09;&#x1f534; A. Path Stabilization for the Kinematic Model&#xff08;基于运动学模型的路径稳定&#xff09;&#x1f7e5; 1&#xff09;Pure Pursuit&#xff08;纯追踪&#xff09;&#x1…

H3C SecParh堡垒机任意用户登录与远程执行代码漏洞

H3C SecParh堡垒机任意用户登录与远程执行代码漏洞1.H3C SecParh堡垒机任意用户登录漏洞1.1.漏洞描述1.2.漏洞影响1.3.漏洞复现1.3.1.登录页面1.3.2.构建URL1.4.总结2.H3C SecParh堡垒机远程命令执行漏洞2.1.漏洞描述2.2.漏洞影响2.3.漏洞复现2.3.1.登录页面2.3.2.构建URL2.4.总…

python-pptx 操作PPTx幻灯片文件删除并替换图片

python-pptx 操作PPTx幻灯片文件删除并替换图片 作者&#xff1a;虚坏叔叔 博客&#xff1a;https://xuhss.com 早餐店不会开到晚上&#xff0c;想吃的人早就来了&#xff01;&#x1f604; 一、原理 通过查找ppt中的图片指纹替换 二、操作流程 原始ppt如下&#xff1a; 根据…

[单片机] MCU串口发送C方案优化

应用场景&#xff1a; 主频不高非操作系统的单片机&#xff0c;需要在while循环中发送 数据到上位机&#xff0c;当数据较长时&#xff0c;会让发送的过程会让其他操作有卡顿感。为了解决这个问题&#xff0c;需采用一种方法&#xff1a;在每次大循环中只发一个字节数据&#x…

HTML5+CSS3(一)-全面详解(学习总结---从入门到深化)

目录 ​编辑 第一个前端程序 学习效果反馈 前端工具的选择与安装 前端常见开发者工具 浏览器 VSCode中文语言包安装&#xff1a; 学习效果反馈 VSCode开发者工具快捷键 VSCode常用快捷键列表 学习效果反馈 HTML5简介与基础骨架 HTML5的DOCTYPE声明 HTML5基本骨架 html…