AVL树的原理及其实现

news2024/10/5 21:24:33

文章目录

  • 前言
  • 了解AVL树
  • AVL树的特点
  • AVL树的节点
  • 调整方案
    • 右单旋
      • 为什么要右单旋呢?
      • 右单旋代码
    • 左单旋
      • 为什么要左单旋?
      • 左单旋代码
    • 左右双旋
      • 左右双旋之后平衡因子的情况
      • 左右双旋代码实现
    • 右左双旋
      • 右左双旋代码:
  • 简单测试

前言

回顾我们对于二叉搜索树的了解,二叉搜索树的效率跟树的高度成反比。而且,数据插入的随机性导致普通的二叉搜索树甚至可能退化成一条“单链表”,这样的结构查找起来时间复杂度直奔O(N),这无疑是我们不想看到的。我们希望,无论数据怎么插入,二叉搜索树的结构都应该保持平衡,即看起来更加接近于完全二叉树或者满二叉树·。AVL树因此诞生。

了解AVL树

AVL树又叫平衡二叉搜索树,得名于它的发明者Georgy Adelson-Velsky和Evgenii Landis。它的出现很好的解决了二叉搜索树的效率退化问题。AVL树的特点就是任一一个节点的左右子树的高度差不超过1。这个特点可以使二叉搜索树的高度始终保持在logn的级别,从而保证了查找效率稳定在O(logn)级别。具体是怎么实现这一特点的呢?下面结合代码给您讲解。

AVL树的特点

  • AVL树具有二叉搜索树的性质,即对于任意一个节点,左子树的值均小于根节点的值,右子树的值均大于根节点的值。
  • 每个节点都有一个平衡因子属性(可视为一个int变量),记录着左右子树的高度差(一般是右子树高度减去左子树高度
  • 查找、删除、插入的时间复杂度均为O(logn).

AVL树的节点

根据AVL树的特点,AVL树的每一个节点都得有一个记录左右子树高度差的平衡因子。除此之外,还需要有一个指向父节点的指针。具体如下:

		//节点信息
	template<class K, class V>
	class AVLNode {
	public:
		AVLNode(const pair<K, V>& kv)
			:_bf(0)
			, left(nullptr)
			, right(nullptr)
			,father(nullptr)
		{
			
		}
		int _bf;//平衡因子
		pair<K, V> _kv;
		AVLNode<K, V>* left;
		AVLNode<K, V>* right;
		AVLNode<K, V>* father;
	};

调整方案

下面所述代码中,平衡二叉树的平衡因子均表示右子树高度减去左子树高度。
当我们向一颗平衡二叉树里插入一个元素,插入节点的祖先节点的平衡因子都有可能会因此改变。从当前新节点的父节点开始向上遍历,重复以下几个步骤:

  1. 用一个指针指cur向新节点表示当前节点,一个指针pa指向其父节点。
  2. 如果cur节点在pa的左子树中,那么这个pa节点的平衡因子减一,否则加一。
  3. 更新平衡因子之后,观察pa平衡因子大小。如果为0,则说明当前pa节点向上的所有父节点的平衡因子都不会改变。
  4. 如果·pa·平衡因子为-1或者1,则说明当前节点符合平衡二叉树节点特征,但是其父节点还不一定,于是继续向上遍历
  5. 如果pa平衡因子为2或者-2,则说明当前节点失衡了(左右子树的高度差大于1)!我们需要调整。

调整方案具体如下:

右单旋

如果cur的平衡因子为-1,且pa的平衡因子为-2。那么我们选择让pa节点右单旋
在这里插入图片描述

上图就是这种情况**。矩形表示一颗子树**。右旋过之后就变成了这样:
在这里插入图片描述

为什么要右单旋呢?

或者说为什么右单旋之后,pacur的平衡因子会为0呢?

假设在没有旋转的时候,pa的左子树高度为N,则右子树高度为N-2。即上图黄色矩形的表示的树的高度为N-2。
由于cur的平衡因子为-1,则cur左子树的高度为N-1,右子树的高度为N-2。即上图蓝色矩形表示的树的高度为N-2。

所以,我们完成这样一个右单旋之后,对于cur来说,左子树的高度依旧是N-1,但是右子树的高度变成了N-1,平衡因子也就变成了0。
对于pa来说,它的右子树高度依旧是N-2,但是左子树高度变成了N-2,平衡因子也变成了0。

右单旋之后还需不需要继续往上调整呢呢?答案是不用了,由于curpa的平衡因子都是0了,再往上的节点的平衡因子保持不变。

继续思考,平衡因子是没问题了,但是这样旋转会不会改变二叉搜索树的性质呢?答案也是不会的。所谓的右单旋,就是把pa变成cur的右孩子,原本cur是pa的左孩子,pa节点包括pa的右子树的值均大于以cur子树的值。旋转之后cur的左子树的值依旧小于cur节点的值,cur右子树的值依旧大于cur节点的值。对于pa来说也是如此。

右单旋代码

//右单旋
void RotateR(pNode pa) {
	pNode subL = pa->left;
	pNode subLR = subL->right;

	pa->left = subLR;
	if (subLR) subLR->father = pa;

	subL->right = pa;
	pNode ppa = pa->father;
	pa->father = subL;
	
	if (pa == _root) {
		_root = subL;
		subL->father = nullptr;
	}
	else {
		if (pa == ppa->left) {
			ppa->left = subL;
		}
		else {
			ppa->right = subL;
		}
		subL->father = ppa;
	}

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

左单旋

如果cur的平衡因子为1,且pa的平衡因子为2。那么我们选择让pa节点左单旋
在理解右单旋的原理之后,对于左单旋也就容易理解了,因为两者的旋转方式都是一样的,只不过“方向”不同。
在这里插入图片描述
这种情况我们就需要对pa进行左单旋,调整之后就变成了下图所示:
在这里插入图片描述

为什么要左单旋?

参考右单旋。

假设在没有旋转的时候,pa的左子树高度为N,则右子树高度为N+2。即上图黄色矩形的表示的树的高度为N。
由于cur的平衡因子为1,则cur右子树的高度为N+1,左子树的高度为N。即上图蓝色矩形表示的树的高度为N。

所以,我们完成这样一个左单旋之后,对于cur来说,右子树的高度依旧是N+1,但是左子树的高度变成了N+1,平衡因子也就变成了0。
对于pa来说,它的左子树高度依旧是N,但是右子树高度变成了N,平衡因子也变成了0。

左单旋代码

//左单旋
void RotateL(pNode pa) {
	pNode subR = pa->right;
	pNode subRL = subR->left;

	pa->right = subRL;
	if (subRL) subRL->father = pa;

	subR->left = pa;
	pNode ppa = pa->father;
	pa->father = subR;

	if (pa == _root) {
		_root = subR;
		subR->father = nullptr;
	}
	else {
		if (pa == ppa->left) {
			ppa->left = subR;
		}
		else {
			ppa->right = subR;
		}
		subR->father = ppa;
	}

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

左右双旋

如果cur的平衡因子为1,且pa的平衡因子为-2。那么我们选择先让pa节点的左孩子左单旋。然后再让pa右单旋

这种情况只对pa进行右单旋不能保证让所有节点平衡,我们自能
在这里插入图片描述
图中的h表示子树的高度
在这里插入图片描述

仔细观察上图AVL树左右双旋的过程,就能明白为什么要双旋能让每个节点再次平衡了。值得注意的是,在双旋之后,pa于subL节点的平衡因子并不是固定的,它们随着新节点插入到subLR的位置的而变化。
例如:
在这里插入图片描述

左右双旋之后平衡因子的情况

  • 情况(1)如果新节点插入到subLR的左子树中,在双旋之后,这个新节点会变成subL的右子树节点,此时subL的平衡因子为0,pa的平衡因子为1
  • 情况(2)如果新节点插入到subLR的右子树中,双旋之后,这个新结点则会变成pa的左子树节点,此时pa的平衡因子为0,subL的平衡因子为-1
  • 情况(3)如果新插入节点本身就是subLR节点,即此时subLR的平衡因子为0。也就意味着双旋之后,pa和subL的平衡因子为0.
  • 但无论是上面的哪种情况,双旋之后,subLR的平衡因子都是0.

那更新pa和subL的平衡因子时如何区分是以上哪种情况呢?看subLR的平衡因子。如果是-1,表示新节点在subLR的左子树中,即情况(1)。如果是1,说明新节点在subLR的右子树中,即情况(2)。如果是0,表示新节点就是subLR本身,即情况(3)。

左右双旋代码实现

有了上面左右单旋的代码,左右双旋就能通过使用封装它们的函数来实现了:

//左右双旋
void RotateLR(pNode pa) {
	pNode subL = pa->left;
	pNode subLR = subL->right;

	int subLR_bf = subLR->_bf;

	RotateL(subL);
	RotateR(pa);

	if (subLR_bf == -1) {
		pa->_bf = 1 ;
		subL->_bf = 0;
	}
	else if (subLR_bf == 1) {
		pa->_bf = 0;
		subL->_bf = -1;
	}
	else if (subLR_bf == 0) {
		pa->_bf = 0;
		subL->_bf = 0;
	}
	else {
		//perror("RotateLR");
	}
	return;
}

右左双旋

如果cur的平衡因子为-1,且pa的平衡因子为2。那么我们选择先让pa节点的右孩子右单旋。然后再让pa左单旋
原理和左右双旋一致,只不过旋转的方向相反:
在这里插入图片描述
右左双旋的平衡因子的情况参考左右双旋,我这里就不做过多赘述了。

同样右左双旋的代码可以使用单旋的接口来实现

右左双旋代码:

//右左双旋
void RotateRL(pNode pa) {
	pNode subR = pa->right;
	pNode subRL = subR->left;

	int subRL_bf =subRL->_bf;

	RotateR(subR);
	RotateL(pa);

	if (subRL_bf == -1) {
		pa->_bf = 0;
		subR->_bf = 1;
	}
	else if (subRL_bf == 1) {
		pa->_bf = -1;
		subR->_bf = 0;
	}
	else if (subRL_bf == 0) {
		pa->_bf = 0;
		subR->_bf = 0;
	}
	else {
		//perror("RotateRL");
	}

}

简单测试

通过上面的学习,现在我们就能基本实现AVL树的插入操作了。为了检查代码的正确性,下面给出一组测试数据插入到AVL树中,并遍历输出观察结果:

#include<iostream>
#include"myAVLTree.h"
using namespace std;
using namespace k_val;

int main() {
	int a[10] = { 1,7,8,3,4,9,10,2,6,5 };
	AVLTree<int, int> avl;
	for (int i = 0; i < 10; i++) {
		avl.Insert({ a[i],a[i] });
 	}

	avl.InOrder();

	return 0;
}

在这里插入图片描述
如果想观察·内部结构,可以自行打开调试窗口一一查看。

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

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

相关文章

保研面试408复习 4——操作系统、计网

文章目录 1、操作系统一、文件系统中文件是如何组织的&#xff1f;二、文件的整体概述三、UNIX外存空闲空间管理 2、计算机网络一、CSMA/CD 协议&#xff08;数据链路层协议&#xff09;二、以太网MAC帧MTU 标记文字记忆&#xff0c;加粗文字注意&#xff0c;普通文字理解。 1、…

值得推荐的10+REST API测试工具

什么是API&#xff1f; API是一个软件解决方案&#xff0c;作为中介&#xff0c;使两个应用程序能够相互交互。以下一些特征让API变得更加有用和有价值&#xff1a; 遵守REST和HTTP等易于访问、广泛理解和开发人员友好的标准。API不仅仅是几行代码&#xff1b;这些是为移动开…

qml 和 c++类的数据交互

1、 新建一个需要交互的C++类 1)添加QObject头文件 2)添加自QObject的继承 3)添加Q_OBJECT宏 4)使用Q_PROPERTY,定义两个交互的属性,并设置读写的方法和变更属性的信号。 5)添加方法、槽函数和变量 2、在main.cpp中添加实例化对象的QML上下文 1)添加需要QML交互的…

Kubernetes学习-集群搭建篇(一) 搭建Master结点

&#x1f3f7;️个人主页&#xff1a;牵着猫散步的鼠鼠 &#x1f3f7;️系列专栏&#xff1a;Kubernetes渐进式学习-专栏 &#x1f3f7;️个人学习笔记&#xff0c;若有缺误&#xff0c;欢迎评论区指正 目录 1. 前言 2. 集群搭建方式 3. 环境说明 4. 利用kubeadm初始化Ma…

应该在哪里找海外ip代理?

出于学习工作&#xff0c;或者游戏娱乐的需求&#xff0c;许多人需要使用海外代理ip。那么我们该如何寻找到合适的、正规的、安全的海外代理ip呢&#xff1f; 首先&#xff0c;我们需要明白使用海外IP代理可能带来的风险&#xff0c;包括隐私泄露、网络速度变慢、安全风险以及可…

百融云创回购计划加速落实 机构看好中长期吸引力

单日回购近400万港元B类股份&#xff0c;一站式服务的AI科技领航者百融云创&#xff08;百融云-W,6608.HK&#xff09;的回购计划正在加速落实。 此前&#xff0c;在百融云创2023年年度业绩公告的同时&#xff0c;该公司一并披露将在2024年不时在公开市场购回总金额不超过2.5亿…

原生微信小程序canvas签名功能

半个月前百度搜出来的&#xff0c;没存书签现在不知道是哪篇文章了&#xff0c;再搜也没搜出来那篇文章&#xff0c;还好当时把代码复制到本地跑了一下&#xff0c;现在再发csdn存一下。 sign.js Page({data: {ctx: null,width: null,height: null,drawCount: 0,drawState: &…

WebStorm开发插件

WebStorm开发插件 开发 WebStorm 插件是一项令人兴奋的任务&#xff0c;它可以帮助提升开发效率&#xff0c;定制 IDE 来满足个人或团队的需求。在这份指南中&#xff0c;我将向你介绍如何开始开发 WebStorm 插件&#xff0c;并提供一些实用的技巧和建议。 1. 准备工作 在开…

“幽灵“再临!新型攻击瞄准英特尔CPU;微软Outlook漏洞被俄利用,网络间谍攻击捷克德国实体 | 安全周报0510

1. 微软Outlook漏洞被俄罗斯APT28利用&#xff0c;捷克德国实体遭网络间谍攻击&#xff01; 捷克和德国于周五透露&#xff0c;他们成为与俄罗斯有关的APT28组织进行的长期网络间谍活动的目标&#xff0c;此举遭到欧洲联盟&#xff08;E.U.&#xff09;、北大西洋公约组织&…

视频拼接融合产品的产品与架构设计(二)

视频拼接融合产品的产品与架构设计一 以上是第一期&#xff0c;以前思考的时候还是比较着急&#xff0c;现在思考的更多了&#xff0c;现实世界的拼接更加需要我们沉下心来做&#xff0c;尤其是对于更多画面&#xff0c;画面更加清晰怎么做 本篇章不在于其他功能&#xff0c;在…

SpringBoot的图片上传

简介 该文档旨在介绍一个基于Spring Boot框架的简单文件上传功能的实现方式。本文档将详细介绍相关代码的功能和配置以及如何使用它们。 样例 技术栈 Spring Boot&#xff1a;一个用于快速开发基于Spring的应用程序的框架。Thymeleaf&#xff1a;一个用于在Web应用程序中创建…

孔板流量计和孔板流量计真的不一样

孔板流量计和孔板流量计真的不一样&#xff0c;无论您是追求品质&#xff0c;还是注重实用功能&#xff0c;我们的产品都能让您心动不已。让您轻松享受到现代科技所带来的便利&#xff0c;尽情展现自己不一样的魅力。 用途【1-5-9】 孔板流量计为煤矿瓦斯抽放而设的计算瓦斯抽…

vue3.0(五) reactive全家桶

文章目录 1 reactive1.1 reactive的应用1.2 reactive的特点1.3 reactive的注意1.4 reactive的局限性 2 toRefs3 isReactive4 shallowReactive5 readonly5.1 readonly 详细信息5.2 readonly函数创建一个只读的响应式对象5.3 如何修改嵌套在只读响应式对象中的对象? 6 isReadonl…

SG3225EEN在PAM4光模块和400G,QSFP-DD光模块中的应用

爱普生晶振SG3225EEN&#xff0c;156.25MHz在PAM4光模块和QSFP-DD光模块中的应用。光模块市场已发展至400G光模块&#xff0c;那么PAM4光模块和400G QSFPDD光模块有哪些区别呢?SG3225EEN又是怎么应用在PAM4光模块和QSFP-DD光模块中的呢? 首先介绍的是PAM4光模块:PAM4是PAM(脉…

爱思控AQMDBLS-Ax/Bx/Mx/T 系列无刷电机驱动器使用

履带车配置 AQMDBLS-Ax/Bx/Mx/T 系列无刷电机驱动器使用大致需要以下几个步骤。 1&#xff09; 阅读用户使用说明书、了解驱动器的性能、明确控制目的&#xff1b;2&#xff09; 根据电机和驱动器的接线原理&#xff0c;正确接线&#xff1b;3&#xff09; 使用 485 通讯控制&…

FileLink跨网文件摆渡系统:保障数据安全,促进业务高效协同

FileLink跨网文件摆渡系统&#xff0c;作为一种先进的文件传输解决方案&#xff0c;正逐渐在企业级应用中崭露头角。它打通了不同网络之间的隔阂&#xff0c;使得文件能够安全、高效地从一个网络传输到另一个网络。 FileLink跨网文件摆渡系统基于先进的加密技术和数据传输协议&…

亚信安慧AntDB:解锁数智化的新时代

亚信安慧AntDB的融合实时的特性使得它在数据库领域独树一帜。传统的数据库系统往往只能追求数据的准确性和一致性&#xff0c;但在实际的业务场景中&#xff0c;这些特性并不能满足企业的需求。AntDB的出现打破了传统束缚&#xff0c;为企业带来了全新的数据处理方式&#xff0…

【Linux网络编程】高级IO——五种IO模型

高级IO 1.什么是IO&#xff1f;什么是高效的IO&#xff1f;2.有那些IO的方式&#xff1f;这么多的方式&#xff0c;有那些是高效的&#xff1f;3.五种IO模型4.高级IO重要概念5.非阻塞IO 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x…

shopro商城 源码搭建/部署/上线/运营/售后/更新

基于Fastadmin和Uniapp进行开发的多平台&#xff08;微信公众号、微信小程序、H5网页、Android-App、IOS-App&#xff09;购物商城&#xff0c;拥有强大的店铺装修、自定义模板、路由同步、多端支付&#xff08;微信&#xff0c;支付宝&#xff09;、多规格商品、运费模板、多地…

突发!31篇文章被撤!这本Springer旗下SCI,开始大面积撤稿,原因涉及同行评议!

【欧亚科睿学术】 近期&#xff0c;期刊JOURNAL OF COMBINATORIAL OPTIMIZATION撤回了31篇文章&#xff0c;大部分文章都是由中国学者参与。主要原因是包括但不限于&#xff1a;编辑处理和同行评议过程的妥协&#xff0c;不恰当或不相关的参考文献&#xff0c;或者不在期刊或客…