C++ AVL树(旋转)

news2024/11/25 4:58:39

我们之前学习了搜索二叉树,我们知道普通的搜索二叉树会有特殊情况出现使得二叉树的两枝极其不平衡形成我们通俗说的歪脖子树:

这样的树一定会使得我们的增删查的效率变低;为了避免这种极端的情况出现,在1962年有两位伟大的俄罗斯数学家Adelson-VelskyLandis发明的;它们通过旋转使得我们的树的任意节点的左右子树高度差不超过2;使得搜索二叉树总会是一颗平衡的搜索二叉树;

我们怎么知道我们的左右子树的高度差呢?

AVL树的节点

我们这个时候就需要在搜索二叉树的基础上对我们的节点进行修改了,给我们的节点增加一个平衡因子_balance factor(_bf);

_bf平衡因子

介绍_bf:

_bf是如何计算的呢?创作者规定 _bf=右子树的深度-左子树的深度; 也就是说,我们每次我们插入一个新节点到我们当前节点的左边的时候_bf需要减一,当新节点插入到我们当前节点的右边的时候_bf加一;

template<class K,class V>
struct AVLTreeNode
{
	typedef AVLTreeNode<K,V> node;
	node* _left;
	node* _right;
	node* _parent;
	pair<K,V> _data;
	int _bf;//balance factor平衡因子
	AVLTreeNode(const pair<K, V>& data)
		:_left(nullptr)
		, _right(nullptr)
		,_parent(nullptr)
		,_data(data)
		,_bf(0)
	{}
};

可以看到,我们的AVL树的成员增加了_bf平衡因子和_parent父亲节点的指针;

有了描述每个节点的条件,要开始组织起来这些节点了,我们需要插入一个个节点;

插入节点

一开始插入节点是与搜索二叉树相同,我们需要先找到节点插入的位置,然后再将新增节点连接到我们的原树的叶子节点的末端,这个时候插入就完成了;

不同的是插入完成后,我们需要更新我们父亲节点的_bf平衡因子;采用上方所说的插入位置在父亲节点左侧时,父亲节点_bf-1,插入在父亲节点右侧时当前节点_bf+1;更新完成父亲节点后向上不断遍历修改父亲的父亲节点;而这个遍历我们又需要分情况遍历;

插入操作(和搜索二叉树中操作相同)
if (_root == nullptr)
{
	_root = new node(data);
	return true;
}
node* cur = _root;
node* parent = _root;
while (cur)
{
	if (data.first < cur->_data.first)
	{
		parent = cur;
		cur = cur->_left;
	}
	else if (data.first > cur->_data.first)
	{
		parent = cur;
		cur = cur->_right;
	}
	else
	{
		return false;
	}
}
cur = new node(data);
cur->_parent = parent;
if (data.first < parent->_data.first)
{
	parent->_left = cur;
}
else
{
	parent->_right = cur;
}
以上是普通的插入操作,下面需要进行平衡因子更新

更新_bf

情况1:父亲节点的_bf更新为1或-1

如果父节点的_bf为1或者1时就代表我们的父节点的左边或者右边新插入了节点,我们我们当前父节点为根的子树高度增加了,从而我们又会影响到父亲节点的父亲节点;所以我们需要继续所以向上遍历让父亲节点成为新插入的节点,更新父亲节点的父亲节点成为parent(父亲),更新新的父亲的_bf;

 情况2:父亲节点的_bf更新为0

 此时说明我们的父亲节点为根节点的树原本是不平衡的,但是由于我们插入的新节点补齐了这颗树;使得父亲节点的_bf变为了0,这个时候由于父亲节点为根的树高度没有变化;所以父亲节点上层的节点自然也不需要更新_bf所以这个时候可以直接退出遍历了;

情况3:父亲节点的_bf绝对值大于1

这个时候,我们需要进行旋转来降低父节点为根的树高度了

循环向上更新_bf
while (parent)
{
	
    if (cur == parent->_left)
	{
		parent->_bf--;
	}
	else
	{
		parent->_bf++;
	}
	if (parent->_bf == 1 || parent->_bf == -1)//-1or1时需要继续更新平衡因子
	{
		cur = parent;
		parent = parent->_parent;	
	}
	else if (parent->_bf == 0)//因子为0是将树补齐了不需要再向上更新了
	{
		break;
	}
	else if (parent->_bf == 2 || parent->_bf == -2)//2or-2时需要旋转减小树的高度
	{
		if (parent->_bf == -2 && parent->_left->_bf == -1)//左子树的左子树高右旋转
		{
			RotateR(parent);
		}
		else if (parent->_bf == 2 && parent->_right->_bf == 1)//右子树的右子树高左旋转
		{
			RotateL(parent);
		}
		else if (parent->_bf == -2 && parent->_left->_bf == 1)//左子树的右子树高,先右子树左旋再左子树右旋
		{
			RotateLR(parent);
		}
		else if (parent->_bf == 2 && parent->_right->_bf == -1)//右子树的左子树高,先左子树右旋再右子树左旋
		{
			RotateRL(parent);
		}
		else
		{
			assert(false);
		}
		break;
	}
}

 旋转

这个操作就是AVL最精华的部分了;我们知道是当树左右两边高度相差超高1的时候就需要旋转了;旋转主要是靠画图理解;

我们需要注意的是我们旋转的树是一般是子树(父节点为根时为整棵树);

而旋转又分为四种旋转情况:

为了方便描述我们将当前树的根节点同一叫根节点;

单旋

情况1:根节点_bf为-2根节点左节点_bf为-1时(右单旋)

当根节点_bf为-2时说明我们这棵树的左子树高度要比右子树高2个节点的高度,根左节点的_bf为-1又说明我们左子树的左子树比左子树的右子树高1个节点的高度,也就是如下图:

我们需要进行单旋,因为是左树高,所以需要进行右旋转,将根节点旋转为左节点的右节点

右单旋
void RotateR(node* parent)
{
	node* subl = parent->_left;
	node* sublr = subl->_right;

	parent->_left = sublr;
	if (sublr)
		sublr->_parent = parent;

	node* ppnode = parent->_parent;

	subl->_right = parent;
	parent->_parent = subl;

	if (ppnode == nullptr)
	{
		_root = subl;
		_root->_parent = nullptr;
	}
	else
	{
		subl->_parent = ppnode;
		if (ppnode->_left == parent)
		{
			ppnode->_left = subl;
		}
		else
		{
			ppnode->_right = subl;
		}
	}
	parent->_bf = subl->_bf = 0;
	return;
}

 情况2:根节点_bf为2根节点右节点_bf为1时(左单旋)

这种情况就是情况1的对立边,这次是右树的右子树高,我们需要进行左单旋;

左单旋
void RotateL(node* parent)
{
	node* subr = parent->_right;
	node* subrl = subr->_left;

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

	node* ppnode = parent->_parent;
	subr->_left = parent;
	parent->_parent = subr;

	if (ppnode == nullptr)
	{
		_root = subr;
		_root->_parent = nullptr;
	}
	else
	{
		if (ppnode->_left == parent)
		{
			ppnode->_left = subr;
		}
		else
		{
			ppnode->_right = subr;
		}
		subr->_parent = ppnode;
	}
	parent->_bf = subr->_bf = 0;
}

 单旋之后我们的parent和左节点的_pf都为0;

双旋

情况3:根节点_pf为-2根节点的左节点_pf为1(左右双旋)

这种情况就是我们的根节点左树高,但左数中的右树高,我们需要进行双旋:

如何左右双旋如图:

 

值得注意的是双旋我们的_bf是需要更新的;我们需要通过sublr的_bf来判断更新值

 代码实现

void RotateLR(node* parent)
{
	node* subl = parent->_left;
	node* sublr = subl->_right;
	int flag = sublr->_bf;
	RotateL(subl);//左旋右子树(树中树)
	RotateR(parent);//右旋左子树
	//更新平衡因子
	if (flag == 1)//插入位置为树中树的右树
	{
		parent->_bf = 0;
		subl->_bf = -1;
		sublr->_bf = 0;
	}
	else if (flag == -1)
	{
		parent->_bf = 1;
		subl->_bf = 0;
		sublr->_bf = 0;
	}
	else if (flag == 0)
	{
		parent->_bf = 0;
		subl->_bf = 0;
		sublr->_bf = 0;
	}
	else
	{
		assert(false);
	}
}

情况4:根节点_pf为2根节点的右节点_pf为-1(右左双旋)

此情况就是不同与单旋时的单一枝高,而是一枝中的另一枝高;

 

 同样的我们旋转之后更新我们的_bf;

代码实现

void RotateRL(node* parent)
{
	node* subr = parent->_right;
	node* subrl = subr->_left;
	int flag = subrl->_bf;
	RotateR(subr);//右旋左子树(树中树)
	RotateL(parent);//左旋右子树
	//更新平衡因子
	if (flag == 1)//插入位置为树中树的右树
	{
		parent->_bf = -1;
		subr->_bf = 0;
		subrl->_bf = 0;
	}
	else if (flag == -1)
	{
		parent->_bf = 0;
		subr->_bf = 1;
		subrl->_bf = 0;
	}
	else if (flag == 0)
	{
		parent->_bf = 0;
		subr->_bf = 0;
		subrl->_bf = 0;
	}
	else
	{
		assert(false);
	}
}

 当上面的旋转和更新_bf都完成的时候,这棵树我们插入就一定是我们的AVL树了;

为了验证我们的树是否真的成为了AVL树我们需要通过检查算法来比较每个节点的_bf是否正确

void _print(node* root)
{
	if (root == nullptr)
		return;
	_print(root->_left);
	cout << root->_data.first << " ";
	_print(root->_right);
}

bool judgeBalance()
{
	return _judgeBalance(_root);
}

bool _judgeBalance(node*root)
{
	if (root == nullptr)
		return true;
	int hl = getDeep(root->_left);//hightLeft
	int hr = getDeep(root->_right);//hightRight
	int judge = hr - hl;
	if (judge != root->_bf)
	{
		cout << root->_data.first << "这个节点的_pf没处理好" << endl;
		return false;
	}

	return _judgeBalance(root->_left) && _judgeBalance(root->_right);
}

void testAVLTree1()
{
	AVLTree<int, int>t;
	srand(time(0));
	for (int i = 0; i < 100; i++)
	{
		int x = rand();
		t.insert(make_pair(x, x));
	}
	t.print();
	cout << t.judgeBalance() << endl;
}

此时我们的树就是一颗完全正确的AVL树 ;

我们只实现了AVL树的插入算法,删除算法还没有学习;

这是我的完整的AVL树代码:

C++代码/AVL树 · future/my_road - 码云 - 开源中国 (gitee.com)

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

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

相关文章

集成电路企业tapeout,如何保证机台数据准确、完整、高效地采集?

Tapeout即流片&#xff0c;集成电路行业中将CDS最终版电路图提交给半导体制造厂商进行物理生产的过程。在芯片设计与制造的流程中&#xff0c;Tapeout是非常重要的阶段&#xff0c;包括了布局&#xff08;Layout&#xff09;、连线&#xff08;Routing&#xff09;、分析&#…

阿里云服务器租用费用价格表_一年和1个月收费标准(2024版)

2024年阿里云服务器租用费用&#xff0c;云服务器ECS经济型e实例2核2G、3M固定带宽99元一年&#xff0c;轻量应用服务器2核2G3M带宽轻量服务器一年61元&#xff0c;ECS u1服务器2核4G5M固定带宽199元一年&#xff0c;2核4G4M带宽轻量服务器一年165元12个月&#xff0c;2核4G服务…

【论文速读】| MASTERKEY:大语言模型聊天机器人的自动化越狱

本次分享论文为&#xff1a;MASTERKEY: Automated Jailbreaking of Large Language Model Chatbots 基本信息 原文作者&#xff1a;Gelei Deng, Yi Liu, Yuekang Li, Kailong Wang, Ying Zhang, Zefeng Li, Haoyu Wang, Tianwei Zhang, Yang Liu 作者单位&#xff1a;南洋理工…

记录一次官网访问很慢的情况

客户查看云监控,带宽未超限,客户取的是1分钟的原生值,也就是1分钟也是个平均值。 但是客户的原始值&#xff0c;其实就是1分钟内的平均值。所以客户的瞬时超限&#xff0c;其实是看不出来的。但是后端同事从实时监控里面可以看到超限的情况。 客户升带宽后&#xff0c; 发现还…

超详细工具Navicat安装教程

Navicat是一款功能强大的数据库管理工具&#xff0c;可用于管理多种类型的数据库&#xff0c;包括MySQL、MariaDB、SQL Server、SQLite、Oracle和PostgreSQL等。以下是Navicat工具的一些主要特点和功能&#xff1a; 一.功能介绍 跨平台支持 多种数据库支持 直观的用户界面 数据…

【Qt 学习笔记】输入框实现helloworld | QLineEdit的使用

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ 输入框实现helloworld | QLineEdit的使用 文章编号&#xff1a;Qt 学习…

【原创教程】EPLAN中伺服的制图方法

首先在EPLAN里制作伺服之前,需要有伺服的手册,根据手册里的各个引脚号的说明来制图,这里我们讲解西门子和三菱这两种品牌型号的。 1、下图是西门子的伺服,型号为:6SL3040-1LA01-0AA0 2、第一步我们需要绘制出黑盒来表示伺服的整体外框 选择插入—盒子—黑盒 3、在图纸…

MQTT中QOS级别

MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;是一种轻量级的通信协议&#xff0c;在物联网和消息传递系统中广泛应用。MQTT 提供了三个不同的 QoS&#xff08;Quality of Service&#xff09;等级&#xff0c;用于确保消息的可靠性和传输效率。本文将详细…

通过SSH在苹果手机上查看系统文件:远程访问iOS文件系统的方法

​ 目录 引言 用户登录工具和连接设备 查看设备信息&#xff0c;电池信息 查看硬盘信息 硬件信息 查看 基带信息 销售信息 电脑可对手机应用程序批量操作 运行APP和查看APP日志 IPA包安装测试 注意事项 引言 苹果手机与安卓手机不同&#xff0c;无法直接访问系统文件…

游戏陪玩平台开发 定制专属陪玩平台-移交源码二次开发,线下可改陪诊,陪伴,家政等功能

线下陪玩接单服务软件系统搭建&#xff08;APP&#xff0c;h5小程序&#xff0c;公众号开发&#xff09;&#xff0c;陪玩接单服务小程序开发搭建&#xff0c;陪玩接单服务系统开发设计&#xff0c;陪玩接单服务软件开发制作&#xff0c;陪玩接单服务平台开发方案 随着人们生活…

记Postman参数化

因为需要在WEB页面上处理部分数据&#xff0c;手动操作太慢&#xff0c;所以考虑使用接口方式处理&#xff0c;因急于使用&#xff0c;用Python Request的方式&#xff0c;写代码也来得慢&#xff0c;故采用Postman加外部文件参数化方式来实现。 接口请求是Post方式&#xff0c…

流域生态系统水-碳-氮耦合过程模拟

原文链接&#xff1a;流域生态系统水-碳-氮耦合过程模拟https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247599933&idx1&sn64dd4dae8b54e7f2c4a18a2729f423d4&chksmfa8206dacdf58fcc4ff9cb95443bdbd238b0f38f0616bbe53c093f68c851f2526a82898c69d2&…

Bessie‘s Birthday Cake (Hard Version)

题目链接 CodeTON Round 8 (Div. 1 Div. 2, Rated, Prizes!) C2. Bessie’s Birthday Cake (Hard Version) 思路&#xff1a; 其实可以先做一下easy version。 先不选点&#xff0c;已有的点我们肯定能加多少边就加多少&#xff0c;而且手玩后发现一个规律&#xff0c;就是…

Lua环境下载与配置

这里介绍如何下载已经编译好的Lua环境&#xff0c;如何配置Lua环境。 如希望自己从源码编译Lua环境&#xff0c;请自行搜索资料。 第一步&#xff1a;下载编译好的lua环境 打开下面链接&#xff0c;然后根据指引下载。 The Programming Language Luahttps://www.lua.org/hom…

基于卷积神经网络的苹果等级分类系统(pytorch框架)【python源码+UI界面+前端界面+功能源码详解】

功能演示&#xff1a; 苹果等级分类系统&#xff0c;基于vgg16&#xff0c;resnet50卷积神经网络&#xff08;pytorch框架&#xff09;_哔哩哔哩_bilibili &#xff08;一&#xff09;简介 基于卷积神经网络的苹果等级分类系统是在pytorch框架下实现的&#xff0c;系统中有两…

神经网络与深度学习(二)

一、深度学习平台 张量&#xff08;Tensor&#xff09; 是一个物理量&#xff0c;对高维(维数 ≥ 2) 的物理量进行“量纲分析” 的一种工具。简单的可以理解为&#xff1a;一维数组称为矢量&#xff0c;二维数组为二阶张量&#xff0c;三维数组为三阶张量 计算图 用“结点”…

03-Linear Regression

什么是回归算法 回归算法是一种有监督算法回归算法是一种比较常用的机器学习算法&#xff0c;用来建立“解释”变量(自变量X)和观测值 (因变量Y)之间的关系; 从机器学习的角度来讲&#xff0c;用于构建一个**算法模型(函数)**来做属性 ( X ) (X) (X) 与标签 ( Y ) (Y) (Y) 之…

Docker in Docker原理与实战探索

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

在 Windows 中安装部署并启动连接 MongoDB 7.x(命令行方式启动、配置文件方式启动、将启动命令安装为系统服务实现开机自启)

MongoDB 的下载 下载地址&#xff1a;https://www.mongodb.com/try/download/community 这里需要对 MongoDB 的版本号说明一下&#xff1a; MongoDB 版本号的命名规则是 x.y.z&#xff0c;当其中的 y 是奇数时表示当前的版本为开发版&#xff0c;当其中的 y 是偶数时表示当前的…

OpenHarmony实战开发-使用一次开发多端部署实现一多设置典型页面

介绍 本示例展示了设置应用的典型页面&#xff0c;其在小窗口和大窗口有不同的显示效果&#xff0c;体现一次开发、多端部署的能力。 1.本示例使用一次开发多端部署中介绍的自适应布局能力和响应式布局能力进行多设备&#xff08;或多窗口尺寸&#xff09;适配&#xff0c;保…