AVL树如何维持平衡

news2024/12/23 5:20:22

1.AVL树的特性

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

一棵AVL树是空树,或者是具有以下性质的二叉搜索树

-它的左右子树都是AVL树

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

用右子树减左子树高度表示左右子树高度之差(平衡因子),下图用蓝色表示节点的平衡因子,如图为一棵AVL树

 因为要控制平衡因子,AVL树节点相比于普通二叉树节点增加了:平衡因子、父节点指针(便于找到父节点控制平衡因子),模拟AVL树节点的定义如下

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

 

2.AVL插入时如何维持平衡

AVL树插入时首先要遵循二叉搜索树的规则,找到对应插入位置,注意节点_pParent的链接

2.1找到插入位置插入并链接 

Node* newnode = new Node(x);
Node* cur = _root;
Node* parent = nullptr;

if (_root == nullptr)
{
	_root = newnode;
	return true;
}
//使用cur找到插入位置
while (cur)
{
	if (cur->_val == x)
	{
		return false;
	}
	else if (cur->_val < x)
	{
		parent = cur;
		cur = cur->_pRight;
	}
	else if (cur->_val > x)
	{
		parent = cur;
		cur = cur->_pLeft;
	}
}

//链接节点
if (x < parent->_val)
{
	parent->_pLeft = newnode;
}
else
{
	parent->_pRight = newnode;
}
newnode->_pParent = parent;
cur = newnode;

 在插入前,这棵树就是一块AVL树遵循AVL树的规则,在插入后可能会破坏规则,就要继续向祖先更新、查看平衡因子。

2.2更新平衡因子

在插入后parent节点平衡因子存在3种情况,有的情况发现此子树的高度不变就不必向上继续更新

若parent的平衡因子,用一个循环实现向上更新

更新后平衡因子存在以下几种情况

1.平衡因子 == 0

说明parent插入前不平衡,插入在短的那边,插入后平衡了,插入后高度不变不需要往上更新

2.平衡因子 == 1或-1

说明parent插入前平衡,插入后不左右子树高度差改变的,那parent所在树高度更新,需继续往上更新

3.parent的平衡因子 == 2或-2

说明parent插入前平衡因子 == 1 或 -1,插入在长的那边了,加剧了parent的不平衡,此时已经违反规则,需要旋转处理调整

while (parent)
{
	//使用循环向上更新
	//更新平衡因子
	if (cur == parent->_pLeft)
	{
		parent->_bf--;
	}
	else if (cur == parent->_pRight)
	{
		parent->_bf++;
	}


	//继续向上更新
	//若已经更新到根,停止
	if (parent == nullptr)
	{
		break;
	}
	//1.若此时parent已经平衡(==0),说明插入节点使子树平衡,并没有增加子树高度,不用往上更新
	if (parent->_bf == 0)
	{
		break;
	}
	//2.若此时parent为弱平衡(_bf == -1/1),说明插入节点使原本平衡的树高度更新,需继续往上更新
	else if (parent->_bf == -1 || parent->_bf == 1)
	{
		
	}
	//3.若此时parent已经失衡(_bf == -2/2),说明插入节点使原本弱平衡的树加剧失衡,需要旋转处理、调整
	else if (parent->_bf == -2 || parent->_bf == 2)
	{
		//单独的一边高(subLL、subRR高),单旋
		if (parent->_bf == -2 && parent->_pLeft->_bf == -1)
		{
			RotatoR(parent);
		}
		else if (parent->_bf == 2 && parent->_pRight->_bf == 1)
		{
			RotatoL(parent);
		}
		//subRL、subLR高,双旋。例:先将subR子树旋转为subRR高的情况,再使用单次左旋
		else if (parent->_bf == -2 && parent->_pLeft->_bf == 1)
		{
			RotatoLR(parent); 
		}
		else if (parent->_bf == 2 && parent->_pRight->_bf == -1)
		{
			RotatoRL(parent);
		}
		//旋转后这棵子树高度并没有增加,不向上继续更新
		break;
	}
	else
	{
		assert(false);
	}
	//迭代,继续向上更新
	cur = parent;
	parent = parent->_pParent;

}

旋转最后得到的结果:

1.遵循搜索树的规则

2.控制平衡,降低高度

而旋转时分为以下几种情况

2.3旋转调整

2.1.1.subRR/subLL长的时候树的旋转调整

如图,若插入在subRR处,parent节点的平衡因子违反了规则,需要调整此子树使之符合规则。

若插入在subLL处,与插入在subRR处类似

下图中a、b、c为抽象的树,h为抽象的树的高度,h = 0时表示空

思想:将parent及其左子树链接到subR的左边使parent子树高度与subRL一样高

具体操作为将parent链接到subR的左边,将subRL链接到parent的右边

代码实现如下,记得要更新平衡因子

	//单次左旋,适用于subRR长的情况
	//思想:将原来根放到subR的左边使parent子树高度与subRL一样高
	void RotatoL(Node* parent)
	{
		Node* subR = parent->_pRight;
		Node* subRL = subR->_pLeft;
		Node* parentParent = parent->_pParent;

		//先链接节点
		if (subRL != nullptr)
			subRL->_pParent = parent;
		parent->_pRight = subRL;

		parent->_pParent = subR;
		subR->_pLeft = parent;

		subR->_pParent = parentParent;
		if (parentParent == nullptr)
		{
			//若parentParent为空则原Parent为整个树的根
			_root = subR;
		}
		else
		{
			if (subR->_val < parentParent->_val)
			{
				parentParent->_pLeft = subR;
			}
			else if (subR->_val > parentParent->_val)
			{
				parentParent->_pRight = subR;
			}
		}

		//再更新平衡因子
		parent->_bf = subR->_bf = 0;

	}

 

2.1.2.subRL/subLR长的时候的调整

如图,插入节点后subRL长,此时无法像上面那样使用左旋使subRR与subRL链接到parent右边树一样高

若插入在subLR处,调整方法类似

图1.subRL长时的插入

这时我们可以想办法旋转使subRR变长,我们将subRL再具体细分,h为抽象树的高度,h == 0 时表示40为新插入节点

这时我们可以先调整 subR为根的子树使用一次旋转使得subR所在子树较长,变为subRR长的情况可以单次旋转调整,最后再单次右旋

整体旋转方法如下图

记得更新平衡因子,若初始为b长,parent最后平衡因子为0,subR最后平衡因子为1;若初始为c长parent最后平衡因子为-1,subR最后平衡因子为0;若subRL为新插入节点,则h == 0则a、b、c、d都为空,parent和subR最后平衡因子都为0

 代码如下

//先右旋再左旋,适用于subRL高的情况
//思想:先将subR子树旋转为subRR高的情况,再使用单次左旋
void RotatoRL(Node* parent)
{
	Node* subR = parent->_pRight;
	Node* subRL = subR->_pLeft;
	//可能subRLL长或subRLR长,通过记录subRL原来的bf调整最后bf
	int bf = subRL->_bf;

	//两次旋转,旋转后平衡因子有误
	RotatoR(subR);
	RotatoL(parent);

	//再更新平衡因子
	subRL->_bf = 0;
	if (bf == 1)
	{
		subR->_bf = 0;
		parent->_bf = -1;
	}
	else if (bf == -1)
	{
		subR->_bf = 1;
		parent->_bf = 0;
	}
	else
	{
		subR->_bf = 0;
		parent->_bf = 0;
	}
}

3.插入的整体代码

 

bool Insert(T& x)
{
	Node* newnode = new Node(x);
	Node* cur = _root;
	Node* parent = nullptr;

	if (_root == nullptr)
	{
		_root = newnode;
		return true;
	}
	//使用cur找到插入位置
	while (cur)
	{
		if (cur->_val == x)
		{
			return false;
		}
		else if (cur->_val < x)
		{
			parent = cur;
			cur = cur->_pRight;
		}
		else if (cur->_val > x)
		{
			parent = cur;
			cur = cur->_pLeft;
		}
	}

	//链接节点
	if (x < parent->_val)
	{
		parent->_pLeft = newnode;
	}
	else
	{
		parent->_pRight = newnode;
	}
	newnode->_pParent = parent;
	cur = newnode;

	while (parent)
	{
		//使用循环向上更新
		//更新平衡因子
		if (cur == parent->_pLeft)
		{
			parent->_bf--;
		}
		else if (cur == parent->_pRight)
		{
			parent->_bf++;
		}


		//继续向上更新
		//若已经更新到根,停止
		if (parent == nullptr)
		{
			break;
		}
		//1.若此时parent已经平衡(==0),说明插入节点使子树平衡,并没有增加子树高度,不用往上更新
		if (parent->_bf == 0)
		{
			break;
		}
		//2.若此时parent为弱平衡(_bf == -1/1),说明插入节点使原本平衡的树高度更新,需继续往上更新
		else if (parent->_bf == -1 || parent->_bf == 1)
		{
			
		}
		//3.若此时parent已经失衡(_bf == -2/2),说明插入节点使原本弱平衡的树加剧失衡,需要旋转处理、调整
		else if (parent->_bf == -2 || parent->_bf == 2)
		{
			//单独的一边高(subLL、subRR高),单旋
			if (parent->_bf == -2 && parent->_pLeft->_bf == -1)
			{
				RotatoR(parent);
			}
			else if (parent->_bf == 2 && parent->_pRight->_bf == 1)
			{
				RotatoL(parent);
			}
			//subRL、subLR高,双旋。例:先将subR子树旋转为subRR高的情况,再使用单次左旋
			else if (parent->_bf == -2 && parent->_pLeft->_bf == 1)
			{
				RotatoLR(parent); 
			}
			else if (parent->_bf == 2 && parent->_pRight->_bf == -1)
			{
				RotatoRL(parent);
			}
			//旋转后这棵子树高度并没有增加,不向上继续更新
			break;
		}
		else
		{
			assert(false);
		}
		//迭代,继续向上更新
		cur = parent;
		parent = parent->_pParent;

	}

	return true;
}

//单次左旋,适用于subRR长的情况
//思想:将原来根放到subR的左边使parent子树高度与subRL一样高
void RotatoL(Node* parent)
{
	Node* subR = parent->_pRight;
	Node* subRL = subR->_pLeft;
	Node* parentParent = parent->_pParent;

	//先链接节点
	if (subRL != nullptr)
		subRL->_pParent = parent;
	parent->_pRight = subRL;

	parent->_pParent = subR;
	subR->_pLeft = parent;

	subR->_pParent = parentParent;
	if (parentParent == nullptr)
	{
		//若parentParent为空则原Parent为整个树的根
		_root = subR;
	}
	else
	{
		if (subR->_val < parentParent->_val)
		{
			parentParent->_pLeft = subR;
		}
		else if (subR->_val > parentParent->_val)
		{
			parentParent->_pRight = subR;
		}
	}

	//再更新平衡因子
	parent->_bf = subR->_bf = 0;

}



//单次右旋,与左旋思想类似
void RotatoR(Node* parent)
{
	Node* subL = parent->_pLeft;
	Node* subLR = subL->_pRight;
	Node* parentParent = parent->_pParent;

	if (subLR != nullptr)
		subLR->_pParent = parent;
	parent->_pLeft = subLR;

	parent->_pParent = subL;
	subL->_pRight = parent;

	subL->_pParent = parentParent;
	if (parentParent == nullptr)
	{
		_root = subL;

	}
	else
	{
		if (parent == parentParent->_pLeft)
		{
			parentParent->_pLeft = subL;
		}
		else if (parent == parentParent->_pRight)
		{
			parentParent->_pRight = subL;
		}
	}

	//再更新平衡因子
	parent->_bf = subL->_bf = 0;
}

//先右旋再左旋,适用于subRL高的情况
//思想:先将subR子树旋转为subRR高的情况,再使用单次左旋
void RotatoRL(Node* parent)
{
	Node* subR = parent->_pRight;
	Node* subRL = subR->_pLeft;
	//可能subRLL长或subRLR长,通过记录subRL原来的bf调整最后bf
	int bf = subRL->_bf;

	//两次旋转,旋转后平衡因子有误
	RotatoR(subR);
	RotatoL(parent);

	//再更新平衡因子
	subRL->_bf = 0;
	if (bf == 1)
	{
		subR->_bf = 0;
		parent->_bf = -1;
	}
	else if (bf == -1)
	{
		subR->_bf = 1;
		parent->_bf = 0;
	}
	else
	{
		subR->_bf = 0;
		parent->_bf = 0;
	}
}

//先左旋再右旋
void RotatoLR(Node* parent)
{
	Node* subL = parent->_pLeft;
	Node* subLR = subL->_pRight;
	int bf = subLR->_bf;

	//两次旋转,旋转后平衡因子有误
	RotatoL(subL);
	RotatoR(parent);

	//再更新平衡因子
	subLR->_bf = 0;
	if (bf == 1)
	{
		subL->_bf = -1;
		parent->_bf = 0;
	}
	else if (bf == -1)
	{
		subL->_bf = 0;
		parent->_bf = 1;
	}
	else
	{
		subL->_bf = 0;
		parent->_bf = 0;
	}
}

 在实现后可以中序遍历整棵树,检查是否为二叉搜索树。并且检查其平衡因子是否符合规则,确定其为平衡树。

//获取高度
int Height()
{
	return _Height(_root);
}
//判断是否为平衡树
bool IsBalanceTree()
{
	return _IsBalanceTree(_root);
}

//判断是否为平衡树
bool _IsBalanceTree(Node* root)
{
	// 空树也是AVL树
	if (root == nullptr)
	{
		return true;
	}

	int leftTreeHeight = _Height(root->_pLeft);
	int rightTreeHeight = _Height(root->_pRight);
	int diff = rightTreeHeight - leftTreeHeight;

	//验证root是不是平衡树
	if (root->_bf == diff && diff >= -1 && diff <= 1)
	{
		//验证其左右子树是不是平衡树
		return _IsBalanceTree(root->_pLeft) && _IsBalanceTree(root->_pRight);
	}
	else
	{
		return false;
	}

}	
  

 //获取高度
int _Height(Node* root)
{
	if (root == nullptr)
	{
		return 0;
	}
	int leftTreeHeight = _Height(root->_pLeft);
	int rightTreeHeight = _Height(root->_pRight);
    return (leftTreeHeight > rightTreeHeight) ? leftTreeHeight + 1 : rightTreeHeight + 1;
}

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

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

相关文章

【工具使用】使用Docsify搭建个人文档网站

检查Node.js安装状态 首先&#xff0c;打开命令提示符&#xff08;CMD&#xff09;&#xff0c;输入以下命令以验证Node.js是否已经安装在您的电脑上&#xff1a; node -v安装Docsify CLI工具 接下来&#xff0c;通过以下命令全局安装Docsify的命令行工具&#xff1a; npm …

保护企业知识产权!推荐十款源代码加密软件

在现代软件开发中&#xff0c;源代码是企业最重要的资产之一。然而&#xff0c;源代码的泄露可能导致巨大的经济损失和知识产权问题。为帮助企业保护其源代码安全&#xff0c;以下推荐十种源代码加密软件&#xff0c;确保你的知识产权不受侵犯。 1. Ping32 Ping32不仅是一款终…

Biomamba求职| 国奖+4篇一作SCI

转眼间我也要参加秋招啦&#xff0c;认真的求职帖&#xff0c;各位老师/老板欢迎联系~其它需要求职的小伙伴也欢迎把简历发给我们&#xff0c;大家一起找工作。 一、基本信息 姓名&#xff1a;Biomamba 性别&#xff1a;男 出厂年份&#xff1a;1998 籍贯&#xff1a;浙江…

FileLink文件摆渡系统应用场景深入分析(一文读懂)

在现代企业的运营中&#xff0c;文件传输与管理的效率直接影响业务的流畅性与安全性。FileLink文件摆渡系统凭借其强大的功能和灵活的应用场景&#xff0c;成为企业信息流动的重要工具。本文将深入分析FileLink文件摆渡系统的几种典型应用场景&#xff0c;展示其在各行业中的价…

【docker】要将容器中的 livox_to_pointcloud2 文件夹复制到宿主机上

复制文件夹 使用 docker cp 命令从容器复制文件夹到宿主机&#xff1a; docker cp <container_id_or_name>:/ws_livox/src/livox_to_pointcloud2 /path/to/host/folder sudo docker cp dandong_orin_docker:/ws_livox/src/livox_to_pointcloud2 /home

AI时代AI提示词学习路线

AI时代&#xff0c;万丈高楼平地起&#xff0c;AI提示词就是基石&#xff01; AI提示词是工具&#xff0c;你不应该先学各种技巧再开始用&#xff0c;而应该先用起来再学习各种技巧。 AI时代AI提示词学习路线&#xff1a; 1、养成习惯&#xff1a;遇事不决问AI。 2、带着问题…

2024.10月7~10日 进一步完善《电信资费管理系统》

一、新增的模块&#xff1a; 在原项目基础上&#xff0c;新增加了以下功能&#xff1a; 1、增加AspectJ 框架的AOP 异常记录和事务管理模块。 2、增加SpringMVC的拦截器&#xff0c;实现登录 控制页面访问权限。 3、增加 Logback日志框架&#xff0c;记录日志。 4、增加动态验…

代码随想录算法训练营第四十六天 | 647. 回文子串,516.最长回文子序列

四十六天打卡&#xff0c;今天用动态规划解决回文问题&#xff0c;回文问题需要用二维dp解决 647.回文子串 题目链接 解题思路 没做出来&#xff0c;布尔类型的dp[i][j]&#xff1a;表示区间范围[i,j] &#xff08;注意是左闭右闭&#xff09;的子串是否是回文子串&#xff0…

【JavaEE】——回显服务器的实现

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯 你们的点赞收藏是我前进最大的动力&#xff01;&#xff01; 希望本文内容能够帮助到你&#xff01;&#xff01; 目录 一&#xff1a;引入 1&#xff1a;基本概念 二&#xff1a;UDP socket API使用 1&#xff1a;socke…

zookeeper ——watcher

这应该不是目录 1.watcher定义和说明1.1定义1.2特性1.3watcher的通知状态Watcher.Event.KeeperState1.4watcher的事件类型Event.EventType1.5基本流程 1.watcher定义和说明 1.1定义 在ZooKeeper中&#xff0c;Watcher是一种机制&#xff0c;用于在节点&#xff08;znode&#…

2024百度云智大会|百度大模型内容安全合规探索与实践

9月25日&#xff0c;2024百度云智大会在北京举办。会上&#xff0c;百度智能云分别针对算力、模型、AI 应用&#xff0c;全面升级百舸 AI 异构计算平台 4.0、千帆大模型平台 3.0 两大 AI 基础设施&#xff0c;并升级代码助手、智能客服、数字人三大 AI 原生应用产品。 在大模型…

网络编程(17)——asio多线程模型IOThreadPool

十七、day17 之前我们介绍了IOServicePool的方式&#xff0c;一个IOServicePool开启n个线程和n个iocontext&#xff0c;每个线程内独立运行iocontext, 各个iocontext监听各自绑定的socket是否就绪&#xff0c;如果就绪就在各自线程里触发回调函数。为避免线程安全问题&#xf…

Auto-Animate:是一款零配置、即插即用的动画工具,可以为您的 Web 应用添加流畅的过渡效果

嗨&#xff0c;大家好&#xff0c;我是小华同学&#xff0c;关注我们获得“最新、最全、最优质”开源项目和高效工作学习方法 用户体验成为了检验产品成功与否的关键因素。而动画效果&#xff0c;作为提升用户体验的重要手段&#xff0c;在网页和应用开发中扮演着举足轻重的角色…

【笔记学习篇】一篇文章搞定Mybatis-快速回顾

概述 5.1.1 Mybatis简介 Mybatis是一款优秀的持久层框架&#xff0c;它以sql为中心&#xff0c;支持定制化sql、存储过程以及高级映射。 使用Mybatis框架&#xff0c;可以无需手动编写基础的JDBC代码、无需手动设置参数和转换结果集到对象。 Mybatis可以使用简单的xml或注解来…

App测试时常用的adb命令

adb 全称为 Android Debug Bridge&#xff08;Android 调试桥&#xff09;&#xff0c;是 Android SDK 中提供的用于管理 Android 模拟器或真机的工具。 adb 是一种功能强大的命令行工具&#xff0c;可让 PC 端与 Android 设备进行通信。adb 命令可执行各种设备操作&#xff0…

银发产业资讯丨蚂蚁集团、金城药业、百联集团、京东健康布局业务

银发经济『新趋势大数据』 AgeNews 每日银发产业大事件速览 2024-10-8 星期二 AgeClub整理 金融监管总局&#xff1a;鼓励险企提供更多养老保障服务 蚂蚁集团等签署合作协议&#xff0c;聚焦智慧医疗领域 金城医药等合作聚焦年长女性健康科技领域 京东健康助力四川发放…

Internet Download Manager6.42免费版下载神器新体验

&#x1f680; 开篇就燃&#xff01;你的下载速度被“TA”承包了 #### &#x1f31f; 初识IDM 6.42&#xff0c;下载界的“超跑”驾到 各位追求效率的小伙伴们&#xff0c;今天小红要来揭秘一款让我彻底告别“龟速”下载的神器——Internet Download Manager (简称IDM) 6.42版&…

日语学习零基础生活日语口语柯桥外语学校|股票用日语怎么说?

在日语中&#xff0c;“股票”可以说&#xff1a; • 株&#xff08;かぶ&#xff09; 这是最常用的表达方式&#xff0c;直接表示“股票”。 例如&#xff1a; 株を買う - 买股票 株を売る - 卖股票 • 株式&#xff08;かぶしき&#xff09; 这个词也是“股票”的意…

学习文档(二)

异常 这是Java 异常类层次结构图概览&#xff1a; Exception 和 Error 有什么区别? 一、概念与本质 Exception&#xff08;异常&#xff09;&#xff1a;异常是在程序运行过程中出现的可预料的、可恢复的不正常情况。例如&#xff0c;试图打开一个不存在的文件时&#xff0…

【数据结构-栈】【位运算优化】力扣3170. 删除星号以后字典序最小的字符串

给你一个字符串 s 。它可能包含任意数量的 ‘’ 字符。你的任务是删除所有的 ’ 字符。 当字符串还存在至少一个 ‘*’ 字符时&#xff0c;你可以执行以下操作&#xff1a; 删除最左边的 ‘’ 字符&#xff0c;同时删除该星号字符左边一个字典序 最小 的字符。如果有多个字典…