C++——AVL平衡树

article/2025/6/27 18:43:28

我们之前已经讲解过了二叉搜索树了。知道它的基本特性后,这次,我们来认识一下AVL平衡树。

在此之前,我们先来想想目前为止,我们可以通过哪些方式来寻找一个数?(有些我们还没有学,大概率后面会更新,包括B树(如果我有时间学到的话,可作为拓展补充学习))

1.暴力搜索(pass掉,时间复杂度太大了)

2.二分查找:(但是有个问题:要求有序,而且伴随着插入删除,它的维护成本比较大)

3.二叉搜索树:(因为有些极端情况下会变成类似链表结构,也会导致它的效率极低,这也是为什么会出现平衡树的原因之一)

4.平衡树(包括AVL平衡树、红黑树)

5.多叉平衡树(B树系列)

6.哈希表

那么,我们现在就来了解一下关于本次的相关内容——AVL平衡树。

了解为什么出现AVL?

我们可以用下面的图来就可以明白:

我们可以看到,当作为极端情况的时候:这是不是就相当于类似链表的结构了,这也就相当于从有序的二叉搜索树退化为单支树了,而且查找元素相当于在顺序表中搜索元素了,效率大大降低。因此为了解决这种极端问题, 俄国的两位数学家发明了这么一种方法:

当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度

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

它的左右子树都是AVL树

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

如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在$O(log_2 n)$,搜索时间复杂度O($log_2 n$)

这么记录它的平衡因子呢?看下图:

一般都是按照:

平衡因子=右子树的高度-左子树的高度

现在,我们来计算一下时间复杂度:

满二叉树  2^h-1=N

AVL树        2^h-x=N

因为高度差的绝对值不超过1,所以:

x的范围是:[1,2^(h-1)-1]

那么2^h-1=N,  两边都除以2---->2^(h-1)=(N-1)/2

因此算得它们的时间复杂度都约等于logN。

那么,可能有人会有疑问:为什么它的高度的绝对值是不超过1呢?

事实上,最佳的平衡条件是每个节点左右子树有着相同的高度,但是,现实很残酷,这会对树的要求太苛刻了,现实我们生活中,很难能够保持插入新元素后,仍保持它们的高度保持平衡的条件,因此,通常会将它的条件退一步:即高度差绝对值不为1.

插入的问题:

现在我们来分析一下它插入过程中的几种情况:

ps:我们的平衡因子主要以这样的运算规则:

平衡因子=右子树-左子树;

上图说到平衡因子==2/-2时需要旋转的情况:

这里根据它的位置分为左旋转与右旋转:

左单旋:

(下面动图来自于网上浏览器)

上面是具体的数,那么当我们化为抽象的,看作一个整体的话是不是也是这样的一个规律呢?现在我们就来具体看看:

我们可以看到:它是一样可以适用的。

 

 

可能第一次看到上面的组合有点懵,确实这个是比较抽象的,我们我们不妨去画出具体的图来理解它:

 

像上面的话,你可以想想,a和b是三种情况都是可以的,并且都是不影响它们之间的。而c的话只能是z情况那种。如果它可以是x和y其中一种的话:就会出现平衡因子是3的情况了。

因此就得出了:插入之前的组合:3*3*1

插入情况是有4种的:两个都有左子树与右子树,一共是4.

所以一共的组合是:3*3*1*4=36种。

所以说这些情况组合是非常多的,我们不可能是完全一一列举出来的,只能通过抽象图来统一计算。

右单旋:

右单旋的情况:跟左单旋的本质是差不多的,只不过逻辑相反而已。

同样,我们来关于它们的组合:也是36种(左单旋那里已经很清楚了)。 

接着更加复杂的情况:

双旋转:

我们经过上面的图分析:当插入的位置不同时,它得出的结果有时符合我们的预期,有时却不符合我们的预期。说明有些情况我们并不适合仅仅用单一的单旋来解决。那么我们该如何去解决呢?我们接着来分析:

 

 

 

 

 

 

好了,有了上面的分析,我们现在来模拟实现一下:

 创建结点:

template<class K,class V>
struct AVTreeNode
{pair<K, V> _kv;AVTreeNode<K, V>* _left;AVTreeNode<K, V>* _right;AVTreeNode<K, V>* _parent;int bf;AVTreeNode(const pair<K,V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr),bf(0){ }
};

1.这里我们使用KV键值模型。

2.通过上面的介绍,我们需要的成员有:键值对_kv,左指针_left,右指针_right,父亲_parent,平衡因子_bf.

3.对于为什么我们直接使用struct?我们之前就已经讲过了,因为后面我们会在它的类外使用到它们的成员,与其把它弄成public,不如直接利用struct默认是public的特性。来满足我们的需求。

4.构造函数进行初始化。把_kv置成kv,指针初始化成空,bf置0.

构造函数这块比较常规就不多讲解了。

AVLTree部分

构造类的成员变量

template<class K,class V>
class AVLTree
{typedef AVTreeNode<K, V> Node;
public:
private:Node* _root;
}

1.这里typedef一下,避免写得太长。

2.它的成员是_root根节点 

构造函数

AVLTree():_root(nullptr){}

插入部分 

bool insert(const pair<K, V>& kv)
{//找到插入的位置if (_root == nullptr){_root = new Node(kv);return true;}Node* cur = _root;Node* parent = nullptr;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;}else{parent->_left = cur;}cur->_parent = parent;//控制平衡因子while (parent){//新增节点在左子树,平衡因子就减减if (cur == parent->_left){parent->bf--;}//新增节点在右子树,平衡因子就加加else //if (cur == parent->_right){parent->bf++;}//如果当前父亲节点的平衡因子变成了0,说明它不会再影响到它祖先的节点了if (parent->bf == 0){break;}//当前父亲节点的平衡因子变为-1/1时,说明它是由原本的0变成的,这说明了,它必将                //会影响到它的祖先,所以需要向上更新它的平衡因子else if (parent->bf == 1 || parent->bf == -1){cur = parent;parent = parent->_parent;}//平衡因子变成了2后,说明需要旋转了。else if (parent->bf == 2 || parent->bf == -2){//需要旋转//左旋if (parent->bf == 2 && cur->bf == 1){RotateL(parent);  }//右旋else if (parent->bf == -2 && cur->bf == -1){RotateR(parent);}//先右旋再左旋else if (parent->bf == 2 && cur->bf == -1){RotateRL(parent);}//先左旋再右旋else if (parent->bf == -2 && cur->bf == 1){RotateLR(parent);}break;}else{assert(false);}}}

至于什么时候左旋?右旋?先左旋后右旋?先右旋再左旋?

我们可以看到最开始那里,按照图里的情况:来得出它的条件:

即:

左旋右旋

先右后左

先左再右

旋转部分: 

左旋转

对着图写代码:不然特别容易错误!!!!

void RotateL(Node* parent){Node* cur = parent->_right;Node* curleft = cur->_left;	parent->_right = curleft;if (curleft){curleft->_parent = parent;}cur->_left = parent;//记录当前父亲的父亲节点,方便旋转后,cur的链接Node* ppnode = parent->_parent;parent->_parent = cur;// 1是prent,3是cur// //  1                    3//    3     ----->    1     5//       5//if (parent == _root)//出错点1if(ppnode==nullptr){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}//更新平衡因子parent->bf = cur->bf = 0;}

右旋转:

ps:看这图来写!!!!不然容易出错!!!!1

void RotateR(Node* parent){Node* cur = parent->_left;Node* curright = cur->_right;parent->_left = curright;if (curright){curright->_parent = parent;}cur->_right = parent;Node* ppnode = parent->_parent;parent->_parent = cur;// 1是prent,3是cur// //       1                  3//     3     ----->       5    1//  5if(ppnode==nullptr)//错误点1//if (parent == _root){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}parent->bf = cur->bf = 0;}

上面分析图里已经很详细了,这里就不多讲解了。

双旋转

先右再左
void RotateRL(Node* parent){Node* cur = parent->_right;Node* curleft = cur->_left;int bf = curleft->bf;RotateR(parent->_right);//写错了//RotateR(parent);RotateL(parent);if (bf == 0){cur->bf = 0;parent->bf = 0;curleft->bf = 0;}else if (bf == -1){cur->bf = 1;curleft->bf = 0;parent->bf = 0;}else if (bf == 1){parent->bf = -1;cur->bf = 0;curleft->bf = 0;}else{assert(false);}}

1.这里要看清楚你要旋转的是那个节点!!!

2.这里的旋转部分不是很大问题。反而更新平衡因子那里有难度问题。而我们这里直接通过观察法,直接强制更新了。

 

先左再右
void RotateLR(Node* parent){Node* cur = parent->_left;Node* ccurright = cur->_right;int bf = ccurright->bf;RotateL(parent->_left);RotateR( parent);if (bf == -1){parent->bf = 1;cur->bf = 0;ccurright->bf = 0;}else if (bf == 0){parent->bf = 0;cur->bf = 0;ccurright->bf = 0;}else if (bf == 1){cur->bf = -1;parent->bf = 0;ccurright->bf = 0;}else{assert(false);}}

同上解法。

其他情况自己推就可以知道了 

那么,我们怎么知道,我们写的AVL树是否正确呢?有什么方法检验它的正确性呢?

这儿提供了一种方法来检验:

检验它的高度差:

    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;}

检验是否平衡的方法: 

bool IsBalance(){return IsBalance(_root);}bool IsBalance(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 << " ";return false;}return abs(rightheight - leftheight) < 2&& IsBalance(root->_left)&& IsBalance(root->_right);}

这里使用:我们使用的公式:平衡因子=右树高度-左树高度

若这个等式并不相等,说明错误了。即造成平衡因子异常。返回错误。

就这样层层递归检查。一旦发现错误立即返回false。

测试:

//int main()
//{
//	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
//	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
//	AVLTree<int, int> t;
//	for (auto& e : a)
//	{
//		//ֶϵ
//		if (e == 14)
//		{
//			int a = 0;
//		}
//		t.insert(make_pair(e,e));
//		cout << e << "->" << t.IsBalance() << endl;
//	}
//	return 0;
//}

上面还是有很大的偶然性的,所以我们这里使用大量的随机数来检验,减少误差:

int main()
{const int N = 10000;vector<int> v;v.reserve(N);srand(time(0));for (int i = 0; i < N; i++){v.push_back(i);}AVLTree<int, int> t;for (auto e : v){t.insert(make_pair(e, e));//cout << e << "->" << t.IsBalance() << endl;}cout << t.IsBalance() << endl;cout << t.Height() << endl;return 0;
}

好了,关于AVL平衡树就分析到这里了,希望大家都有所收获!

最后,到了本次鸡汤环节:


http://www.hkcw.cn/article/jIvFiUtpaO.shtml

相关文章

Blaster - Multiplayer P127-PXXX: 多种武器

P129_ Rocket Projectiles P129_1 创建火箭 配置请自行查看. P129_2 创建火箭发射器 配置请自行查看. P129_3 初始化弹药 这里添加了一个新的武器类型. P129_4 禁止重复添加 CharacerOverlay P130_ Rocket Trails 本节制作了一个奶瓜特效. P131_Spawn Rocket Trails 如果…

基于SpringBoot运动会管理系统设计和实现(源码+文档+部署讲解)

技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论文…

无畏契约 directx runtime修复

无畏契约 directx runtime修复 问题如下 解决办法

端午经济成为消费活力新引擎 民俗体验带动文旅热潮

端午节作为中国首个入选世界非物质文化遗产的传统节日,在今年展现出了多元的文旅消费方式。人们不仅在河湖边观看龙舟竞渡,还在古镇体验民俗技艺、参观文博场馆,享受艺术之美。这些活动不仅展现了中华文化的独特魅力,还成为拉动消费市场的新动力。今年端午假期,“民俗体验…

48岁妻子产子丈夫称孙子比儿子大3岁 28岁女儿喜迎弟弟

6月2日,广东河源一名48岁的再婚女子在怀孕后仅用15分钟就顺利产下孩子。她的28岁女儿对此表示非常高兴,并发文说:“从此多一个人为妈妈保驾护航了。”女子的丈夫提到,他们的孙子比儿子还要大3岁。据此前报道,这名女子发现自己怀孕时已经怀胎7个月,她之前一直以为自己是“…

印尼球员费迪南:目标是连胜中日全取6分,力争直接出线 豪言壮志冲击出线

印尼本土边锋费迪南表示,对于即将到来的18强赛最后两轮比赛,印尼队的目标是连胜中国和日本,全取6分。5月30日是印尼足协主席托希尔的55岁生日,他当时正在巴厘岛参加U23东南亚杯的小组抽签仪式,并与印尼队共进晚餐,庆祝自己和归化队长伊泽斯的生日。托希尔和伊泽斯收到的生…

俄州长宣布奖励向乌无人机投石民众 平民勇阻袭击获赞

俄罗斯伊尔库茨克州州长科布泽夫表示,向乌克兰无人机投掷石块的几名当地男子将获得奖励。此前社交媒体上流传的一段视频显示,几名俄罗斯男子爬上搭载乌克兰无人机的卡车车顶,试图阻止无人机起飞对俄境内发动袭击。俄罗斯国防部通报称,乌克兰当局使用FPV无人机对摩尔曼斯克州…

24岁大学生暗网贩毒3年捞钱超7亿 台大学霸落网

24岁大学生暗网贩毒3年捞钱超7亿 台大学霸落网。据环球网援引台媒报道,近日,美国联邦调查局(FBI)破获暗网毒品交易平台“隐身市场”,而该平台经营者“法老”的真实身份竟是24岁台湾大学资管系学生林睿庠。林睿庠因贩毒资产暴增,3年多其不法所得超过1亿美元(约7.2亿元人民…

嫂子还得多练!塞鸟妻子高铁上感慨中文太难:我不知道“手”中文 高铁学中文挑战大

嫂子还得多练!塞鸟妻子高铁上感慨中文太难:我不知道“手”中文 高铁学中文挑战大!塞尔吉尼奥的妻子社媒晒出在高铁上学中文的照片,并用中葡双语配文:这要求也太高了,我不知道“手”(说)中文责任编辑:0882

【C#】Quartz.NET怎么动态调用方法,并且根据指定时间周期执行,动态配置类何方法以及Cron表达式,有请DeepSeek

&#x1f339;欢迎来到《小5讲堂》&#x1f339; &#x1f339;这是《C#》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解。&#x1f339; &#x1f339;温馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01;&#…

58岁女子山里失联6天5夜奇迹生还 救援队地毯式搜寻成功救出

6月1日,温州市黑马救援队发现一名失联6天5夜的老人。当时老人尚有生命体征,已被送往医院进一步救治。这名58岁的老人于5月27日在温州市鹿城区仰义教堂附近的山中失联,家属和救援队多日寻找未果。6月1日上午,黑马救援队接到鹿城区公安分局指挥中心指令后,组织了40余人,携带…

20250602在Ubuntu20.04.6下修改压缩包的日期和时间

rootrootrootroot-X99-Turbo:~$ ll -rwxrwxrwx 1 rootroot rootroot 36247187308 5月 23 10:23 Android13.0地面站.tgz* rootrootrootroot-X99-Turbo:~$ touch 1Android13.0地面站.tgz rootrootrootroot-X99-Turbo:~$ ll -rwxrwxrwx 1 rootroot rootroot 36247187308 6月…

金价再冲上3300美元 贸易局势推动

5月美股表现亮眼,标普500指数上涨6.15%,纳斯达克指数上涨9.56%,道琼斯指数上涨3.94%。尽管市场波动加剧,标普500和纳斯达克仍创下自2023年11月以来最大月度涨幅。特朗普宣布从6月4日起将进口钢铁关税提高至50%。这一消息导致现货黄金周一跳空高开,重新站上3300美元关口。亚…

graphviz, dot,python批量生成,示例1-10

0&#xff09;运行python脚本 import osdir1 "dot1" allfiles os.listdir(dir1) for i in range(len(allfiles)):ff dir1"\\"allfiles[i]file_name, file_extension os.path.splitext(ff)if not ".txt" file_extension.lower():continuecm…

光伏功率预测 | BiLSTM多变量单步光伏功率预测(Matlab完整源码和数据)

光伏功率预测 | BiLSTM多变量单步光伏功率预测&#xff08;Matlab完整源码和数据&#xff09; 目录 光伏功率预测 | BiLSTM多变量单步光伏功率预测&#xff08;Matlab完整源码和数据&#xff09;效果一览基本介绍程序设计参考资料 效果一览 基本介绍 光伏功率预测 | BiLSTM多变…

云计算数据治理

知识星球&#xff1a;数据书局。打算通过知识星球将这些年积累的知识、经验分享出来&#xff0c;让各位在数据治理、数据分析的路上少走弯路&#xff0c;另外星球也方便动态更新最近的资料&#xff0c;提供各位一起讨论数据的小圈子 1.摘要 云计算可以推动创新和各行业应用的…

「Python教案」字符串格式化操作

课程目标 1&#xff0e;知识目标 能够正确使用%、str.format()以及f-string进行数据格式化。能够分析不同格式化方法的适用场景及优缺点等。能够利用格式化控制符&#xff0c;例如对齐、宽度、精度、进制转换等进行格式化控制。 2&#xff0e;能力目标 能够根据需求选择合适…

空调清洗教程

&#x1f32c;️空调大清洗 流程过于复杂&#xff0c;建议网上找人清洗。如果想自己动手建议两人&#xff0c;可以站高的椅子&#xff08;安全为主&#xff09;。 &#x1f9f0; 工具准备&#xff1a; 螺丝刀螺丝收纳盒手套&#xff08;铝片很锋利&#xff0c;小心被划伤&am…

Docker 与 Harbor 私有仓库:镜像管理与版本控制的完整实践

在容器化大行其道的今天,Docker 镜像作为构建和部署容器化应用的基石,其管理和分发变得尤为关键。虽然 Docker Hub 等公共镜像仓库提供了便利,但在企业级或团队开发环境中,出于安全、效率、合规性和版本控制等考量,搭建一个私有 Docker 镜像仓库已成为不可或缺的一环。 本…

【深入详解】数据在内存中的存储:整数在内存中的存储、大小端字节序和字节序判断、浮点数在内存中的存储

目录 一、整数在内存中的存储 二、大小端字节序和字节序判断 &#xff08;一&#xff09;大小端是什么 &#xff08;二&#xff09;为什么会有大小端 &#xff08;三&#xff09;练习 1、练习(1) 2、练习(2) 3、练习(3) 4、练习(4) 5、练习(5) 6、练习(6) 三、浮点数…