【C++】AVL树(旋转、平衡因子)

🌈个人主页:秦jh_-CSDN博客
🔥 系列专栏:https://blog.csdn.net/qinjh_/category_12575764.html?spm=1001.2014.3001.5482

 9efbcbc3d25747719da38c01b3fa9b4f.gif​ 

目录

前言

AVL树的概念

 节点

插入

AVL树的旋转 

新节点插入较高左子树的左侧---左左:右单旋 

新节点插入较高右子树的右侧---右右:左单旋

新节点插入较高左子树的右侧---左右:先左单旋再右单旋

 新节点插入较高右子树的左侧---右左:先右单旋再左单旋

AVL树的验证 

 AVL树的性能

完整代码


前言

    💬 hello! 各位铁子们大家好哇。

             今日更新了AVL树的相关内容
    🎉 欢迎大家关注🔍点赞👍收藏⭐️留言📝

AVL树的概念

 二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查 找元素相当于在顺序表中搜索元素,效率低下。

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

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

如果一棵二叉搜索树是高度平衡的,它就是AVL树。

插入的总体原则:

  1.  按照搜索树规则插入
  2. 更新插入节点的祖先节点的平衡因子。
    1. 如果插入在父亲左边,父亲的平衡因子--。
    2. 如果插入在父亲右边,父亲的平衡因子++。
    3. 父亲平衡因子==0,则父亲所在子树高度不变,不再继续往上更新,插入结束。
    4. 父亲平衡因子==1or-1,父亲所在子树高度变了,继续往上更新。
    5. 父亲平衡因子==2or-2,父亲所在子树已经不平衡了,需要旋转处理。

 节点

插入

	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->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;//更新平衡因子while (parent) {if (cur == parent->_left){parent->_bf--;}else{parent->_bf++;}if (parent->_bf == 0){//更新结束break;}else if (parent->_bf == 1 || parent->_bf == -1){//继续往上更新cur = parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2){//当前子树出问题了,需要旋转平衡一下break;}else{//理论而言不可能出现该情况assert(false);}}return true;}

上面是插入的大体流程,旋转操作还未给出。

AVL树的旋转 

如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构, 使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:

新节点插入较高左子树的左侧---左左:右单旋 

这里以抽象图进行分析,因为具体的情况有很多种,无法画出。

注意:a子树的情况必须是插入后会引发祖先节点的更新,而不是只是内部变化。如下图情况就不符合要求。

旋转流程:新节点插入在a树中,导致以60为根的二叉树不平衡。所以就要右单旋。

右单旋:把60的左子树高度减少,即把60取出来,让30的右子树变成60的左子树,再把以60为根的树变成30的右子树。30成为新的根。

	void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR) //节点可能为空subLR->_parent = parent;subL->_right = parent; //旧父节点变成subL的右节点Node* ppNode = parent->_parent;  //该不平衡节点可能不是根节点,所以要找到它的父节点parent->_parent = subL;				if (parent == _root)   //如果该节点是根节点{_root = subL;		_root->_parent = nullptr;}else  //不平衡节点只是一棵子树{if (ppNode->_left == parent)  //如果旧父节点等于爷爷节点的左节点,新父节点为爷爷节点的左节点{ppNode->_left = subL;}else{ppNode->_right = subL;}subL->_parent = ppNode;	//新父节点指向爷爷节点。}parent->_bf = subL->_bf = 0;  //只需要修改这两个的平衡因子}

新节点插入较高右子树的右侧---右右:左单旋

参考右单旋。

左单旋和右单旋的调用如下图:

新节点插入较高左子树的右侧---左右:先左单旋再右单旋

单旋用在一边一直高的情况。双旋是先一边高再另一边高的情况。

双旋的的原理就是把折线变成直线,再像处理直线一样旋转。

双旋可以复用单旋,但双旋主要要搞清平衡因子的变化。

第一种情况: 

双旋的结果:60的左边给了30的右边,60的右边给了90的左边,30和90分别成为60的左右,60成为根。

上图是插入b引起的旋转,当插入c时是第二种情况,如下图:

上面两种插入位置的不同,导致最终的平衡因子不同。

第三种情况:

h==0时,60就是新增节点,最终的平衡因子也不同。

	void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right; int bf = subLR->_bf;  //记录未旋转前subLR的平衡因子RotateL(parent->_left);  RotateR(parent);if (bf == -1)  //如果bf为-1,即插入在subLR的左边{subLR->_bf = 0;subL->_bf = 0;parent->_bf = 1;}else if (bf == 1) //插入在subLR的右边{subLR->_bf = 0;subL->_bf = -1;parent->_bf = 0;}else if (bf == 0){subLR->_bf = 0;subL->_bf = 0;parent->_bf = 0;}else{assert(false);}}

 新节点插入较高右子树的左侧---右左:先右单旋再左单旋

参考左右双旋,注意,这里也要讨论那三种情况。 

	void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);subRL->_bf = 0;if (bf == 1){subR->_bf = 0;parent->_bf = -1;}else if (bf == -1){parent->_bf = 0;subR->_bf = 1;}else{parent->_bf = 0;subR->_bf = 0;}}

AVL树的验证 

AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:

  1. 验证其为二叉搜索树。如果中序遍历可得到一个有序的序列,就说明为二叉搜索树
  2. 验证其为平衡树。每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子) 节点的平衡因子是否计算正确 

 因为root是私有的,又因为需要递归检查每棵子树是否平衡,所以可以写一个私有的_IsBalance方法,通过公有的IsBalance方法来调用。

 AVL树的性能

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

完整代码

template<class K, class V>
struct AVLTreeNode
{AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;pair<K, V> _kv;int _bf;AVLTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv),_bf(0){}
};template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public: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->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;//更新平衡因子while (parent) {if (cur == parent->_left){parent->_bf--;}else{parent->_bf++;}if (parent->_bf == 0){//更新结束break;}else if (parent->_bf == 1 || parent->_bf == -1){//继续往上更新cur = parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2){//当前子树出问题了,需要旋转平衡一下if (parent->_bf == -2 && cur->_bf == -1) //左边高,右单旋{RotateR(parent);}else if (parent->_bf == 2 && cur->_bf == 1)//右边高,左单旋{RotateL(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);}}return true;}Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_kv.first < key){cur = cur->_right;}else if (cur->_kv.first > key){cur = cur->_left;}else{return cur;}}return nullptr;}void InOrder(){_InOrder(_root);cout << endl;}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR) //节点可能为空subLR->_parent = parent;subL->_right = parent; //旧父节点变成subL的右节点Node* ppNode = parent->_parent;  //该不平衡节点可能不是根节点,所以要找到它的父节点parent->_parent = subL;				if (parent == _root)   //如果该节点是根节点{_root = subL;		_root->_parent = nullptr;}else  //不平衡节点只是一棵子树{if (ppNode->_left == parent)  //如果旧父节点等于爷爷节点的左节点,新父节点为爷爷节点的左节点{ppNode->_left = subL;}else{ppNode->_right = subL;}subL->_parent = ppNode;	//新父节点指向爷爷节点。}parent->_bf = subL->_bf = 0;  //只需要修改这两个的平衡因子}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;subR->_left = parent;Node* ppNode = parent->_parent;parent->_parent = subR;if (parent == _root){_root = subR;_root->_parent = nullptr;}else{if (ppNode->_right == parent){ppNode->_right = subR;}else{ppNode->_left = subR;}subR->_parent = ppNode;}parent->_bf = subR->_bf = 0;}void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);subRL->_bf = 0;if (bf == 1){subR->_bf = 0;parent->_bf = -1;}else if (bf == -1){parent->_bf = 0;subR->_bf = 1;}else{parent->_bf = 0;subR->_bf = 0;}}void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right; int bf = subLR->_bf;  //记录未旋转前subLR的平衡因子RotateL(parent->_left);  RotateR(parent);if (bf == -1)  //如果bf为-1,即插入在subLR的左边{subLR->_bf = 0;subL->_bf = 0;parent->_bf = 1;}else if (bf == 1) //插入在subLR的右边{subLR->_bf = 0;subL->_bf = -1;parent->_bf = 0;}else if (bf == 0){subLR->_bf = 0;subL->_bf = 0;parent->_bf = 0;}else{assert(false);}}bool IsBalance(){return _IsBalance(_root);}int Height() //树的高度{return _Height(_root);}int Size()  //插入的节点个数{return _Size(_root);}private:int _Size(Node* root){return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;}int _Height(Node* root){if (root == nullptr)return 0;return max(_Height(root->_left), _Height(root->_right)) + 1;}bool _IsBalance(Node* root) {if (root == nullptr) return true;int	leftHeight = _Height(root->_left);int	rightHeight = _Height(root->_right);//如果不平衡if (abs(leftHeight - rightHeight) >= 2){cout << root->_kv.first << endl;return false;}//检查平衡因子是否正确if (rightHeight - leftHeight != root->_bf){cout << root->_kv.first << endl;return false;}return _IsBalance(root->_left)&& _IsBalance(root->_right);}void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_InOrder(root->_right);}private:Node* _root=nullptr; 
};void AVLTreeTest1()
{//int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };AVLTree<int,int> t1;for (auto e : a){t1.Insert({e,e});cout <<"Insert:"<<e<<"->"<< t1.IsBalance() << endl;}t1.InOrder();	cout << t1.IsBalance() << endl;
}

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

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

相关文章

【C++】stack和queue的模拟实现 双端队列deque的介绍

&#x1f525;个人主页&#xff1a; Forcible Bug Maker &#x1f525;专栏&#xff1a; STL || C 目录 &#x1f308;前言&#x1f525;stack的模拟实现&#x1f525;queue的模拟实现&#x1f525;deque&#xff08;双端队列&#xff09;deque的缺陷 &#x1f308;为什么选择…

七、Docker常规软件安装

目录 一、总体步骤 二、安装tomcat 1、docker hub上查找tomcat镜像 三、安装MySQL 1、查看MySQL镜像 2、拉取MySQL镜像到本地,本次拉取MySQL5.7 3、使用MySQL镜像创建容器 4、使用Windows数据库工具&#xff0c;连接MySQL实例 5、常见问题 6、创建MySQL容器实例 7、新…

DDP:微软提出动态detection head选择,适配计算资源有限场景 | CVPR 2022

DPP能够对目标检测proposal进行非统一处理&#xff0c;根据proposal选择不同复杂度的算子&#xff0c;加速整体推理过程。从实验结果来看&#xff0c;效果非常不错 来源&#xff1a;晓飞的算法工程笔记 公众号 论文: Should All Proposals be Treated Equally in Object Detect…

同声传译app哪个好免费?对话交流推荐这5个

暑期到&#xff0c;也是旅游出行的好日子~自打周边不少国家都开放免签政策之后&#xff0c;出国游也变得更加方便了~对于外语水平不高的朋友来讲&#xff0c;想要保证出行体验&#xff0c;其实手上只要备好一个同声传译app就OK&#xff01; 倘若你还不清楚都有哪些同声传译app…

背部筋膜炎的症状及治疗

背部筋膜炎&#xff0c;也称为胸背肌筋膜炎&#xff0c;主要是由于劳损或风寒湿邪侵入引起的。其典型症状主要包括&#xff1a; 1、疼痛&#xff1a;背部筋膜一旦出现炎症性病变&#xff0c;会对周围交感神经组织产生刺激作用&#xff0c;从而引起不同程度的疼痛症状。 2、僵…

NAT:地址转换技术

为什么会引入NAT&#xff1f; NAT&#xff08;网络地址转换&#xff09;的引入主要是为了解决两个问题 IPv4地址短缺&#xff1a;互联网快速发展&#xff0c;可用的公网IP地址越来越少。网络安全&#xff1a;需要一种方法来保护内部网络不被直接暴露在互联网上。 IPv4 &…

低通滤波以及卡尔曼滤波

先讲解几个低通滤波&#xff0c;低通滤波比卡尔曼滤波简单&#xff0c;因为卡尔曼滤波涉及到两个输入量&#xff0c;一个是控制量&#xff0c;一个是观测量&#xff0c;而低通滤波是一个输入量 1&#xff0c;利用工具箱配置低通滤波 参考地址&#xff1a;https://blog.csdn.net…

SystemUIService启动-Android13

SystemUIService启动-Android13 1、SystemUIService启动2、其他SystemUI services启动2.1 Dagger依赖注入2.2 Recents为例 1、SystemUIService启动 SystemUI启动&#xff0c;及其SystemUIService启动 <!-- SystemUi service component --><string name"config_s…

什么是海外仓管理自动化?策略及落地实施步骤指南

作为海外仓的管理者&#xff0c;你每天都面临提高海外仓运营效率、降低成本和满足客户需求的问题。海外仓自动化管理技术为这些问题提供了不错的解决思路&#xff0c;不过和任何新技术一样&#xff0c;从策略到落地实施&#xff0c;都有一个对基础逻辑的认识过程。 今天我们整…

重生奇迹mu的地图名

地图之一&#xff1a;勇者大陆 勇者大陆地处奇迹大陆中央。终年阴雨连绵&#xff0c;气候潮湿闷热。植物由充满黑暗阴森气氛的草地所构成。这里的NPC数量是所有地图中最多的。因为地步交通要冲&#xff0c;所以也是玩家聚集最多的地方。 这里是剑士、魔法师、魔剑士和圣导师初…

vue3关于在线考试 实现监考功能 推流拉流

vue3 关于在线考试 实现监考功能&#xff0c; pc端考试 本质是直播推流的功能 使用腾讯云直播: 在线文档 index.html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><link rel"icon" href"/f…

永磁同步电机控制算法--最大转矩电流比控制(虚拟信号注入法)

目前&#xff0c;国内外相关学者对 MTPA 控制方法进行了一系列的理论研究与仿真分析。通过研究取得的成果综合来看&#xff0c;该控制方法主要有&#xff1a;直接公式计算法、曲线拟合法、查表法、搜索法、高频信号注入法以及参数辨识法等。 之前的文章中已经介绍了直接公式计…

对于“百模大战”,几乎所有大佬的口风都180 °大转变了?

文 | 智能相对论 作者 | 陈泊丞 在2024世界人工智能大会暨人工智能全球治理高级别会议产业发展主论坛上&#xff0c;百度创始人、董事长兼首席执行官李彦宏谈了些对于AI大模型的看法&#xff0c;语惊四座。 他先是指出&#xff0c;“百模大战造成了社会资源的巨大浪费&#x…

现在2024年网络安全真实情况还好就业吗?_2024年网络安全专业到底行不行了

2024年网络安全行业的前景看起来非常乐观。根据当前的趋势和发展&#xff0c;一些趋势和发展可能对2024年网络安全行业产生影响&#xff1a; 5G技术的广泛应用&#xff1a;5G技术的普及将会使互联网的速度更快&#xff0c;同时也将带来更多的网络威胁和安全挑战。网络安全专家…

java-spring boot光速入门教程(超详细!!)

目录 一、引言 1.1 初始化配置 1.2 整合第三方框架 1.3 后期维护 1.4 部署工程 1.5 敏捷式开发 二、SpringBoot介绍 spring boot 2.1 搭建一个spring boot工程 2.2 使用idea创建项目 2.3 在线创建姿势 2.4 项目的目录结构 2.5 项目的运行方式 2.6 yml文件格式 2…

分享外贸工作中常用英文标准表达和英文语句

常用英文表达 报拉格斯最低到岸价 quote the lowest price CIF Lagos经营纺织品多年 be in the line of textiles for many years货物受欢迎 the goods are very popular with customers / have met with a warm reception /be well received/accepted/ enjoy a wide populari…

Java线程死锁及解决方法

多线程环境下&#xff0c;死锁即两个或两个以上的线程去争夺同一个共享资源&#xff0c;而导致互相等待的情况。 要产生死锁&#xff0c;必须满足如下四个条件&#xff1a; 互斥条件&#xff0c;共享资源x和y只能被一个线程占有请求和保持条件&#xff0c;T1持有x&#xff0c…

解读BASE理论:高可用性与性能的完美平衡

Base概念 BASE 理论是一种处理大规模分布式系统中的数据一致性问题的思路。相比于传统的严格一致性&#xff0c;它更灵活&#xff0c;适用于那些需要高可用性和性能的系统。BASE 理论由三个部分组成&#xff1a; 基本可用&#xff08;Basically Available&#xff09; 基本可用…

利用亚马逊云科技云原生Serverless代码托管服务开发OpenAI ChatGPT-4o应用

今天小李哥继续介绍国际上主流云计算平台亚马逊云科技AWS上的热门生成式AI应用开发架构。上次小李哥分享​了利用谷歌云serverless代码托管服务Cloud Functions构建Gemini Pro API​&#xff0c;这次我将介绍如何利用亚马逊的云原生服务Lambda调用OpenAI的最新模型ChatGPT 4o。…

CSAL: the Next-Gen Local Disks for the Cloud——论文泛读

EuroSys 2024 Paper 论文阅读笔记整理 问题 云本地磁盘以其实惠的价格和高性能而极具吸引力。在云本地磁盘中&#xff0c;物理存储设备直接连接到计算服务器&#xff0c;并作为块设备虚拟化到虚拟机&#xff08;VM&#xff09;。在这种设置下&#xff0c;计算节点受其有限的计…