AVL树的底层实现

news2024/11/24 9:35:00

文章目录

  • 什么是AVL树?
  • 平衡因子
  • Node节点
  • 插入
    • 新节点插入较高左子树的左侧
    • 新节点插入较高左子树的右侧
    • 新节点插入较高右子树的左侧
    • 新节点插入较高右子树的右侧
  • 验证是否为平衡树
  • 二叉树的高度
  • AVL的性能

什么是AVL树?

AVL树又称平衡二叉搜索树,相比与二叉搜索树多了平衡的概念,为什么要有平衡二叉搜索树呢?因为二叉搜索树可能会出现退化现象,导致左右子树高度相差较大,甚至退化成单链表的形式,因此我们提出了平衡二叉搜索树,可以有效地解决高度相差过大的情况,平衡二叉搜索树要求任意节点的左右子树高度相差不大于1.

平衡因子

为了解决高度差问题,我们在每一个节点中添加了平衡因子变量,这个变量用于记录该节点的左右子树的高度差。
若规定平衡因子等于右子树高度减去左子树高度,那么如果新增节点的位于当前节点的左侧那么该节点平衡因子-1,如果位于该节点的右侧,则该节点平衡因子+1,并且一直向上修改平衡因子,直到某个节点平衡因子为0,或者到根节点,或者需要进行调整

Node节点

template <class K, class V>
struct AVLTreeNode
{
	pair<K, V> _kv;//pair是库中的类,包含first和second两个成员变量
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	int _bf;//blance factor
	AVLTreeNode(const pair<K, V>& kv)
		:_kv(kv)
		,_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _bf(0)
	{}
};

我们规定平衡因子等于右子树高度减去左子树高度

插入

平衡二叉搜索树也是二叉搜索树,因此它的插入和二叉搜索树类似,都是找到一个合适的节点进行插入,不同的是这棵树要保证每个节点的平衡因子不大于1,因此我们在插入后可能需要对该树的节点进行调整。
需要调整的情况有以下四种:分别是

新节点插入较高左子树的左侧

情况1:
在这里插入图片描述

情况2
在这里插入图片描述

上面两种情况的处理方式是一样的,因为这两种情况都属于较高子树的左边,以图为例,5和10所在位置分别是不平衡节点9的左右子树,而新插入的节点都是位于左子树的根节点5的左侧

思路:如果新插入的节点位于不平衡节点的左子树的左侧,要想让不平衡节点变平衡,就要让高的一侧子树高度-1,低的一侧子树高度+1,这样就可以使得原本高度差为2的左右子树的高度差变为0,同时还要保证二叉树的性质不被破坏,所以还要对6进行处理。
右单旋:不平衡节点以及它的右子树进行,绕不平衡节点的左孩子旋转90度。
右单旋是指不平衡节点以及它的右子树进行旋转

//新节点插入较高左子树的左侧----左左: 右单旋
void RotateR(Node* parent)
{
	++_rotateCount;
	Node* cur = parent->_left;
	Node* curright = cur->_right;
	parent->_left = curright;/
	if (curright)
	{
		//parent->_right = curright;
		curright->_parent = parent;
	}
	cur->_right = parent;
	Node* ppnode = parent->_parent;
	parent->_parent = cur;
	//if (parent = _root)
	if(ppnode == nullptr)
	{
		_root = cur;
		cur->_parent = nullptr;
	}
	else
	{
		if (parent == ppnode->_left)
		{
			//cur = ppnode->_left;
			ppnode->_left = cur;
		}
		else
		{
			//cur = ppnode->_right;
			ppnode->_right = cur;
		}
		cur->_parent = ppnode;
	}
	cur->_bf = parent->_bf = 0;
}

最后不要忘记把新插入节点和那个不满足平衡因子的左孩子的平衡因子都置为0

新节点插入较高左子树的右侧

在这里插入图片描述
新插入节点位于7的左右两侧都属于同一种情况,这里就不做演示了。
先把不满足平衡因子的节点的左孩子进行左旋转,在对不满足平衡因子的节点进行右旋转。

//新节点插入较高左子树的右侧---左右:先左单旋再右单旋
void RotateLR(Node* parent)//先左旋再右旋
{
	Node* cur = parent->_left;
	Node* curright = cur->_right;
	int bf = curright->_bf;
	RotateL(parent->_left);
	RotateR(parent);
	if (bf == 0)
	{
		parent->_bf = 0;
		cur->_bf = 0;
		curright->_bf = 0;
	}
	else if (bf == 1)
	{
		curright->_bf = 0;
		cur->_bf = -1;
		parent->_bf = 0;
	}
	else if (bf == -1)
	{
		curright->_bf = 0;
		cur->_bf = 0;
		parent->_bf = 1;
	}
	else
	{
		assert(false);
	}
}

调整完之后不要忘记对旋转节点的平衡因子进行调整

新节点插入较高右子树的左侧

插入到较高右子树左侧与插入到较高左子树右侧对称,因此调整方式恰好相反,因此是先右旋再左旋

//新节点插入较高右子树的左侧---右左:先右单旋再左单旋
void RotateRL(Node* parent)
{
Node* cur = parent->_right;
Node* curleft = cur->_left;
int bf = curleft->_bf;

RotateR(parent->_right);
RotateL(parent);

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

新节点插入较高右子树的右侧

左单旋是指不平衡节点以及它的左子树进行旋转
同理,与插入到较高左子树的左侧对称,因此是左单旋

//新节点插入较高右子树的右侧----右右:左单旋
void RotateL(Node* parent)
{
	++_rotateCount;
	Node* cur = parent->_right;
	Node* curleft = cur->_left;

	parent->_right = curleft;/
	if (curleft)
	{
		//parent->_right = curleft;
		curleft->_parent = parent;
	}
	cur->_left = parent;
	Node* ppnode = parent->_parent;
	parent->_parent = cur;
	if (parent == _root)
	{
		_root = cur;
		cur->_parent = nullptr;
	}
	else
	{
		if (ppnode->_left == parent)
		{
			ppnode->_left = cur;
		}
		else
		{
			ppnode->_right = cur;
		}
		cur->_parent = ppnode;
	}
	cur->_bf = parent->_bf = 0;
}

验证是否为平衡树

bool IsBlance()
{
	return IsBlance(_root);
}

bool IsBlance(Node* root)
{
	if (root == nullptr)
	{
		return true;
	}
	int leftHeight = Height(root->_left);
	int rightHeight = Height(root->_right);
	if (rightHeight - leftHeight != root->_bf)
	{
		cout << "平衡因子异常:" << root->_kv.first << " " << root->_bf << endl;
		return false;
	}
	return abs(rightHeight - leftHeight) < 2
		&& IsBlance(root->_right)
		&& IsBlance(root->_left);
}

二叉树的高度

int Height()
{
	return Height(_root);
}

int Height(Node* root)
{
	if (root == nullptr)
	{
		return 0;
	}
	int leftHeight = Height(root->_left);
	int rightHeight = Height(root->_right);
	return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}

AVL的性能

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

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

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

相关文章

腾讯云服务器便宜吗?腾讯云服务器怎么买便宜?附优惠链接

首先&#xff0c;咱们来看一下大家最关心的一个问题&#xff1a;“腾讯云服务器便宜吗&#xff1f;”我的答案是&#xff1a;“YES&#xff01;它真的很便宜&#xff01;”比如&#xff0c;轻量2核2G3M服务器&#xff0c;1年只需要88元&#xff0c;是不是很划算&#xff1f;再比…

实例解释遇到前端报错时如何排查问题

前端页面报错&#xff1a; 1、页面报错500&#xff0c;首先我们可以知道是服务端的问题&#xff0c;需要去看下服务端的报错信息&#xff1a; 2、首先我们查看下前端是否给后端传了id: 我们可以看到接口是把ID返回了&#xff0c;就需要再看下p_id是什么情况了。 3、我们再次请…

基于ssm+vue的程序设计课程可视化教学系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

双11背后的中国云厂商:新“标准化”,和调整后的新韧性

降价并不代表一味的压缩自身利润空间&#xff0c;云厂商已经开始向具有更高利润空间的PaaS、SaaS产品腾挪&#xff0c;核心产品在总包占比越来越高。 作者|斗斗 编辑|皮爷 出品|产业家 今年云厂商&#xff0c;全面拥抱双11。 作为中国最大的云计算服务提供商&#xff0…

关于苏州立讯公司国产替代案例(使用我公司H82409S网络变压器和E1152E01A-YG网口连接器产品)

关于苏州立讯公司国产替代案例&#xff08;使用我们公司的H82409S网络变压器和E1152E01A-YG网口连接器产品&#xff09; 苏州立讯公司是一家专注于通信设备制造的企业&#xff0c;他们在其产品中选择了我们公司的H82409S网络变压器和E1152E01A-YG网口连接器&#xff0c;以实现…

在线协作工具都有哪些?推荐这10款

如今&#xff0c;互联网的快速发展不仅改变了我们的生活方式&#xff0c;也改变了我们的工作方式。 特别是对于一些与产品设计相关的公司或团体&#xff0c;网络不仅为其设计提供了稳定的资源和灵感&#xff0c;而且为成员之间的沟通和合作提供了更大的便利。 如果您也需要为…

ke11介绍本地,会话存储

代码顺序: 1.设置input,捕获input如果有多个用属性选择符例如 input[typefile]点击事件.向我们的本地存储设置键值对 2.在点击事件外面设置本地存储表示初始化的值.点击上面的事件才能修改我们想修改的值 会话(session)浏览a数据可以写到本地硬盘,关闭页面数据就没了 本地(…

JPA整合Sqlite解决Dialect报错问题, 最新版Hibernate6

前言 我个人项目中&#xff0c;不想使用太重的数据库&#xff0c;而内嵌数据库中SQLite又是最受欢迎的&#xff0c; 因此决定采用这个数据库。 可是JPA并不支持Sqlite&#xff0c;这篇文章就是记录如何解决这个问题的。 原因 JPA屏蔽了底层的各个数据库差异&#xff0c; 但是…

使用Python的time库来格式化时间

目录 一、引言 二、time库简介 三、使用time库来格式化时间 四、使用time库进行时间戳转换 五、使用time库获取当前时间 六、使用time库进行延时操作 七、使用time库计算时间差 八、使用time库获取系统时间 九、使用time库的其他功能 总结 一、引言 在Python中&…

鸿蒙OS应用开发初体验

什么是HarmonyOS&#xff1f; HarmonyOS&#xff08;鸿蒙操作系统&#xff09;是华为公司开发的一款基于微内核的分布式操作系统。它是一个面向物联网&#xff08;IoT&#xff09;时代的全场景操作系统&#xff0c;旨在为各种类型的设备提供统一的操作系统平台和开发框架。Har…

自媒体剪辑必备,6个音效素材网站,你值得拥有。

这6个剪辑必备的音效素材网站一定要收藏好了&#xff0c;有了这几个网站能让你在剪辑的时候事半功倍&#xff0c;还不用担心版权问题。话不多说&#xff0c;直接上干货。 1、菜鸟图库 https://www.sucai999.com/audio.html?vNTYwNDUx 菜鸟图库是一个综合性素材网站&#xff…

ref属性

Vue 中 可以给标签&#xff08;HTML元素标签、组件标签&#xff09;加上一个 ref 属性&#xff0c;作用就是给 标签加上一个标识&#xff0c;可以通过该标识获取当前需要的DOM元素或者组件实例对象。 通过原生js获取DOM 如果使用原生js来获取DOM元素时&#xff0c;我们最简单的…

Kafka学习笔记(一)

目录 第1章 Kafka概述1.1 消息队列&#xff08;Message Queue&#xff09;1.1.1 传统消息队列的应用场景1.1.2 消息队列的两种模式 1.2 定义 第2章 Kafka快速入门2.1 安装部署2.1.1 集群规划2.1.2 jar包下载2.1.3 集群部署 2.2 Kafka命令行操作 第3章 Kafka架构深入3.1 Kafka工…

23111708[含文档+PPT+源码等]计算机毕业设计基于javaweb的旅游网站前台与后台旅景点

文章目录 **论文截图&#xff1a;****实现&#xff1a;****代码片段&#xff1a;** 编程技术交流、源码分享、模板分享、网课教程 &#x1f427;裙&#xff1a;776871563 下面是系统运行起来后的部分截图&#xff1a; 论文截图&#xff1a; 实现&#xff1a; 代码片段&#xf…

身份证如何加水印?一分钟解决!

身份证是对我们每个人来说都非常重要的东西&#xff0c;在身份证上添加水印&#xff0c;可以明确证件的所有权和合法性&#xff0c;可以在一定程度上防止身份证被用于非法目的&#xff0c;如身份盗用、欺诈等。下面就向大家介绍三种实用的方法和具体操作步骤。 方法一&#xff…

剑指Offer || 116.省份数量

题目 有 n 个城市&#xff0c;其中一些彼此相连&#xff0c;另一些没有相连。如果城市 a 与城市 b 直接相连&#xff0c;且城市 b 与城市 c 直接相连&#xff0c;那么城市 a 与城市 c 间接相连。 省份 是一组直接或间接相连的城市&#xff0c;组内不含其他没有相连的城市。 …

Linux环境下安装部署MySQL8.0以上(内置保姆级教程)- 离线模式

摘要 本文档适用于在无网的情况下手动部署的MySQL。任何版本均可按照如下步骤进行部MySQL。 并且还讲解了如何修改数据库root账号的密码&#xff1b;以及还讲解了如何开启mysql远程访问权限&#xff0c;允许远程连接。 一、安装前准备工作 1.确认目前服务器上是否存在MySQL…

竞赛 题目:基于机器视觉的图像矫正 (以车牌识别为例) - 图像畸变校正

文章目录 0 简介1 思路简介1.1 车牌定位1.2 畸变校正 2 代码实现2.1 车牌定位2.1.1 通过颜色特征选定可疑区域2.1.2 寻找车牌外围轮廓2.1.3 车牌区域定位 2.2 畸变校正2.2.1 畸变后车牌顶点定位2.2.2 校正 7 最后 0 简介 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享…

《QT从基础到进阶·三十三》QT插件开发QtPlugin

插件和dll区别&#xff1a; 插件 插件主要面向接口编程&#xff0c;无需访问.lib文件&#xff0c;热插拔、利于团队开发。即使在程序运行时.dll不存在&#xff0c;也可以正常启动&#xff0c;只是相应插件功能无法正常使用而已&#xff1b; 调用插件中的方法只要dll即可&#x…

Hadoop学习总结(MapRdeuce的词频统计)

MapRdeuce编程示例——词频统计 一、MapRdeuce的词频统计的过程 二、编程过程 1、Mapper 组件 WordcountMapper.java package com.itcast.mrdemo;import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.Text; …