当前位置: 首页 > news >正文

C++_AVL树

       

目录

1、AVL的概念

2、平衡因子的调整概念

3、AVL树的插入

3.1 调整平衡因子代码实现

3.2 右旋操作

3.2 左旋操作 

3.3 双旋-先右旋再左旋

3.4 双旋-先左旋再右旋

3.5 旋转操作的小结

4、AVL的验证与实现

结语


前言:

        在C++中,AVL树是在二叉搜索树的基础上优化而来的,因为二叉搜索树有一个缺陷,即如果插入的数据接近有序或者有序,那么二叉搜索树就会变成一边倒的结构,也就是”单支树“,而”单支树“查找数据效率就会变成和线性数据结构一样的O(N),失去了二叉树本有的优势。 单支树示意图如下:

        因此两位俄罗斯的数学家G.M.Adelson-Velskii 和E.M.Landis1962推出了AVL树的概念,AVL树在每一次插入新节点的同时,会自动调整该树的高度,即不会让每个节点的左右子树高度的绝对值不超过1,因此哪怕面对有序数据的插入也不会让树变成”单支树“,可以使查找数据的效率始终保持在O(log N)。

1、AVL的概念

        AVL树满足以下两个条件:

        1、其每颗子树都是AVL树。

        2、每个节点的左右子树高度(可在节点中新加一个变量-平衡因子表示该节点的左右子树高度)的绝对值不超过1。

        AVL树示意图如下:

        其中,-1表示该节点的左子树高度高于右子树高度1个单位,0表示该节点左右子树高度一样,1表示该节点的右子树高度高于左子树高度1个单位。转换成公式:平衡因子=右子树高度-左子树高度

2、平衡因子的调整概念

        AVL树插入新节点的规则依旧是按照二叉搜索树的规则,即左节点小于根结点,而右节点大于跟节点。只不过AVL新加入了平衡因子的概念,所以每次插入节点后,都需要对平衡因子做出调整,比如插入的节点为该子树的根结点的右边,则根结点的平衡因子需要+1。

        此时,平衡因子的状态就会出现三种情况:

        1、插入节点后,根结点的平衡因子从正负1变为0,这种情况说明插入该节点后这棵树子树得到了平衡,无需做任何处理。

        2、插入节点后,根结点的平衡因子从0变为1或-1,这种情况说明插入的节点破坏了该树的原有平衡,虽然当前子树的根结点的平衡因子的绝对值没有超过1,但是该子树的祖先节点的平衡因子的绝对值可能已经超过1了,所以需要”向上更新“,更改祖先节点的平衡因子。

        3、插入节点后,该树的某个节点的平衡因子的绝对值超过了1,则需要对该子树进行旋转处理。

        具体示意图如下:

        此时会发现,不管新插入的节点在9的右边还是左边,都会导致节点8的平衡因子变成2,因为对于9来说可能在哪边插入节点都行,但是对于节点8来说,这两个插入的节点都在8的右子树,所以会导致8的平衡因子变成2。

        在节点9的左边插入节点的情况如下:


         只有在8的左边插入节点,才不会引发旋转调整,示意图如下:

3、AVL树的插入

        AVL树的插入函数的实现可以分成三步:1、找到插入节点的合适位置。2、调整节点的平衡因子。3、对平衡因子异常的节点进行旋转操作。

        第一步是复用了二叉搜索树的插入逻辑,即判断要插入节点的值是否大于或小于根结点,若大于根结点则往右子树遍历,若小于根结点则往左子树遍历,直到找到节点指向的nullptr处,然后将插入节点与其父母节点相连接。

        第二步调整平衡因子逻辑即上文叙述。

        第三步进行的旋转操作有:左旋、右旋、双旋转(即左右旋的结合),具体如下文。

3.1 调整平衡因子代码实现

        将上述的调整场景转换为代码:

#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>
using namespace std;template<class K, class V>
class AVLTreeNode//节点
{AVLTreeNode<K, V>* _left;//指向左节点AVLTreeNode<K, V>* _right;//指向右节点AVLTreeNode<K, V>* _parent;//指向自己的父母节点pair<K, V> _kv;// 记录数据用的pair类型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//AVL树
{typedef AVLTreeNode<K, V> Node;
public:bool Insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);return true;}Node* parent = nullptr;Node* cur = _root;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;//AVL和二叉搜索树一样不允许有相同数据}}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->_right){parent->_bf++;//插入的位置是节点的右边,则平衡因子++}else{parent->_bf--;//插入的位置是节点的左边,则平衡因子--}if (parent->_bf == 1 || parent->_bf == -1){// 继续更新parent = parent->_parent;cur = cur->_parent;}else if (parent->_bf == 0)//等于0说明平衡了,则不处理{break;}else if (parent->_bf == 2 || parent->_bf == -2){// 需要旋转处理 -- 1、让这颗子树平衡 2、降低这颗子树的高度}else{assert(false);}}return true;}private:Node* _root = nullptr;
};

        以上代码已经实现了合适位置的查找和平衡因子的调整,插入函数的实现还差最后一步,即平衡因子等于2或者-2时旋转操作的实现。

3.2 右旋操作

        右旋操作是针对平衡因子为-2的场景,说明该节点的左子树比右子树要高,需要右旋降低左子树的高度。并且旋转完成后要更新平衡因子,具体操作示意图如下:

        从结果可以看到,原本平衡因子是-2的节点经过旋转操作之后变成了0。并且旋转之后的节点与节点之间的逻辑依然是遵循小于根结点的在左边,大于根节点的在右边。

3.2 左旋操作 

        左旋操作是针对平衡因子为2的场景,说明该节点的右子树比左子树要高,需要左旋降低右子树的高度。并且旋转完成后要更新平衡因子,具体操作示意图如下:

         从结果可以看到,原本平衡因子是2的节点经过旋转操作之后变成了0。

        无论是左旋还是右旋,都要注意两个事项:

        一、拿上述左旋举例,节点12不一定存在左孩子,如果不存在左孩子则10的平衡因子是-1。

        二、节点10不一定是根结点,可能是某个节点的左子树或者右子树,若10只是子树,则旋转后要将节点12与原先节点10的父母节点相连接。

3.3 双旋-先右旋再左旋

        从上述的例子可以发现,插入的节点都处于”边缘“节点下,比如拿上述的左旋举例,若插入的节点是插入到节点11下则如何调整呢?这时候就要用到双选调整,比如上述的节点14作为节点11的孩子插入,则需要以节点12为一棵子树,先进行右旋操作,然后再以根结点10进行左旋操作。

        先右旋再左旋示意图如下:

3.4 双旋-先左旋再右旋

         用上述右旋的例子来进行说明,如果插入的节点是作为节点8的孩子节点,则需要先以节点6为子树进行左旋,然后再以根结点10进行整棵树的右旋。

        先左旋再右旋的示意图如下:

3.5 旋转操作的小结

经过上述旋转操作的细述,大致可以得出以下结论:

一、平衡因子为2的节点,说明该节点的右子树高,因此只需要关注该节点的右孩子,其右孩子的平衡因子会有两种情况:

        1、该节点平衡因子若为1则只需要进行左旋转即可。

        2、该节点平衡因子若为-1则需要进行双旋-先右旋再左旋。

二、平衡因子为-2的节点,说明该节点的左子树高,因此只需要关注该节点的左孩子,其左孩子的平衡因子会有两种情况:

        1、该节点平衡因子若为1则需要进行双旋-先左旋再右旋。

        2、该节点平衡因子若为-1则只需要进行右旋转即可。

        从以上结论可以写出旋转代码。

4、AVL的验证与实现

        判断一棵树是否为AVL树的依据有两点:1、中序遍历的方式打印出来的是有序数据。2、每个节点的平衡因子的绝对值不超过2。

        AVL树的实现代码如下: 

#define _CRT_SECURE_NO_WARNINGS 1
#include <assert.h>
#include <time.h>
#include<iostream>
using namespace std;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; // balance factorAVLTreeNode(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* parent = nullptr;Node* cur = _root;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->_right){parent->_bf++;}else{parent->_bf--;}if (parent->_bf == 1 || parent->_bf == -1){parent = parent->_parent;cur = cur->_parent;}else if (parent->_bf == 0){break;}else if (parent->_bf == 2 || parent->_bf == -2){// 旋转处理:1、让这颗子树平衡 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){RotateLR(parent);}else if (parent->_bf == 2 && cur->_bf == -1){RotateRL(parent);}else{assert(false);}break;}else{assert(false);}}return true;}void InOrder(){_InOrder(_root);cout << endl;}bool IsBalance(){return _IsBalance(_root);}int Height(){return _Height(_root);}private:int _Height(Node* root){if (root == NULL)return 0;int leftH = _Height(root->_left);int rightH = _Height(root->_right);return leftH > rightH ? leftH + 1 : rightH + 1;}bool _IsBalance(Node* root)//观察平衡因子是否有问题{if (root == NULL){return true;}int leftH = _Height(root->_left);int rightH = _Height(root->_right);if (rightH - leftH != root->_bf){cout << root->_kv.first << "平衡因子异常" << endl;return false;}return abs(leftH - rightH) < 2&& _IsBalance(root->_left)&& _IsBalance(root->_right);}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;}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 (parent == _root){_root = subL;_root->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subL;}else{ppnode->_right = subL;}subL->_parent = ppnode;}subL->_bf = parent->_bf = 0;}void RotateLR(Node* parent)//左右旋{Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);if (bf == 1){parent->_bf = 0;subLR->_bf = 0;subL->_bf = -1;}else if (bf == -1){parent->_bf = 1;subLR->_bf = 0;subL->_bf = 0;}else if (bf == 0){parent->_bf = 0;subLR->_bf = 0;subL->_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);if (bf == 1){subR->_bf = 0;parent->_bf = -1;subRL->_bf = 0;}else if (bf == -1){subR->_bf = 1;parent->_bf = 0;subRL->_bf = 0;}else if (bf == 0){subR->_bf = 0;parent->_bf = 0;subRL->_bf = 0;}else{assert(false);}}void _InOrder(Node* root)//中序遍历AVL树{if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);}
private:Node* _root = nullptr;
};int main()
{int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };AVLTree<int, int> t1;for (auto e : a){t1.Insert(make_pair(e, e));}t1.InOrder();cout << t1.IsBalance() << endl;return 0;
}

结语

        以上就是关于AVL的讲解,AVL的重点在于对旋转的理解,理清旋转的逻辑与各个节点之间的逻辑关系尤为重要。最后希望本文可以给你带来更多的收获,如果本文对你起到了帮助,希望可以动动小指头帮忙点赞👍+关注😎+收藏👌!如果有遗漏或者有误的地方欢迎大家在评论区补充,谢谢大家!!

相关文章:

  • 【ArcGIS】统计格网中不同土地利用类型占比
  • Vue/React 前端高频面试
  • 初阶数据结构:二叉树
  • langchain学习笔记(十)
  • SMBGhost漏洞技术分析与防御方案
  • 【C++干货基地】揭秘C++11常用特性:内联函数 | 范围for | auto自动识别 | nullptr指针空值
  • 向日葵、Todesk、teamviewer等工具远程连接电脑时第三方应用显示白屏
  • 编程笔记 Golang基础 045 math包
  • chrome选项页面options page配置
  • AI网址啊
  • 【css面试题】BFC
  • 通过QScrollArea寻找最后一个弹簧并且设置弹簧大小
  • OpenCV 4基础篇| OpenCV图像的裁切
  • leetcode移除元素
  • AzerothCore安装记录
  • JS中 map, filter, some, every, forEach, for in, for of 用法总结
  • 77. Combinations
  • Docker 1.12实践:Docker Service、Stack与分布式应用捆绑包
  • E-HPC支持多队列管理和自动伸缩
  • ERLANG 网工修炼笔记 ---- UDP
  • GitUp, 你不可错过的秀外慧中的git工具
  • Javascripit类型转换比较那点事儿,双等号(==)
  • js中的正则表达式入门
  • SpingCloudBus整合RabbitMQ
  • vue2.0开发聊天程序(四) 完整体验一次Vue开发(下)
  • 更好理解的面向对象的Javascript 1 —— 动态类型和多态
  • 函数式编程与面向对象编程[4]:Scala的类型关联Type Alias
  • 简单基于spring的redis配置(单机和集群模式)
  • 解析带emoji和链接的聊天系统消息
  • 开源地图数据可视化库——mapnik
  • 扑朔迷离的属性和特性【彻底弄清】
  • 如何优雅地使用 Sublime Text
  • 入门级的git使用指北
  • 深入浏览器事件循环的本质
  • 手写双向链表LinkedList的几个常用功能
  • media数据库操作,可以进行增删改查,实现回收站,隐私照片功能 SharedPreferences存储地址:
  • CMake 入门1/5:基于阿里云 ECS搭建体验环境
  • Python 之网络式编程
  • 基于django的视频点播网站开发-step3-注册登录功能 ...
  • ​一文看懂数据清洗:缺失值、异常值和重复值的处理
  • (02)Cartographer源码无死角解析-(03) 新数据运行与地图保存、加载地图启动仅定位模式
  • (二)JAVA使用POI操作excel
  • (附源码)计算机毕业设计ssm高校《大学语文》课程作业在线管理系统
  • (简单) HDU 2612 Find a way,BFS。
  • (南京观海微电子)——I3C协议介绍
  • (提供数据集下载)基于大语言模型LangChain与ChatGLM3-6B本地知识库调优:数据集优化、参数调整、Prompt提示词优化实战
  • (转)Android学习笔记 --- android任务栈和启动模式
  • (转)iOS字体
  • .FileZilla的使用和主动模式被动模式介绍
  • .NET Core跨平台微服务学习资源
  • .net websocket 获取http登录的用户_如何解密浏览器的登录密码?获取浏览器内用户信息?...
  • .NET/C# 项目如何优雅地设置条件编译符号?
  • [⑧ADRV902x]: Digital Pre-Distortion (DPD)学习笔记
  • [AMQP Connection 127.0.0.1:5672] An unexpected connection driver error occured
  • [BZOJ1060][ZJOI2007]时态同步 树形dp