C++ AVL树 详细讲解

目录

一、AVL树的概念

二、AVL树的实现

1.AVL树节点的定义

2.AVL树的插入

3.AVL树的旋转

4.AVL树的验证

三、AVL树的性能

四、完结撒❀


一、AVL树的概念

二叉搜索树虽可以缩短查找的效率,但 如果数据有序或接近有序二叉搜索树将退化为单支树,查
找元素相当于在顺序表中搜索元素,效率低下 。因此,两位俄罗斯的数学家 G.M.Adelson-Velskii
E.M.Landis 1962 发明了一种解决上述问题的方法: 当向二叉搜索树中插入新结点后,如果能保证每个结点的左右 子树高度之差的绝对值不超过 1( 需要对树中的结点进行调整 ) ,即可降低树的高度,从而减少平均 搜索长度。
一棵 AVL 树或者是空树,或者是具有以下性质的二叉搜索树:
● 它的左右子树都是 AVL
● 左右子树高度之差 ( 简称平衡因子 ) 的绝对值不超过 1(-1/0/1)

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

二、AVL树的实现

1.AVL树节点的定义

AVL树节点的定义:

template<class T>
struct AVLTreeNode
{AVLTreeNode(const T& data): _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr), _data(data), _bf(0){}AVLTreeNode<T>* _pLeft;   // 该节点的左孩子AVLTreeNode<T>* _pRight;  // 该节点的右孩子AVLTreeNode<T>* _pParent; // 该节点的双亲T _data;int _bf;                  // 该节点的平衡因子
};

2.AVL树的插入

AVL 树就是在二叉搜索树的基础上引入了平衡因子,因此 AVL 树也可以看成是二叉搜索树。那么
AVL 树的插入过程可以分为两步:
1. 按照二叉搜索树的方式插入新节点
2. 调整节点的平衡因子
bool Insert(const pair<K, V>& kv)
{// 1. 先按照二叉搜索树的规则将节点插入到AVL树中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->_left;}else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else{//插入相同值return false;}}//找到cur所在位置cur = new Node(kv);if (parent->_kv.first > cur->_kv.first){parent->_left = cur;cur->_parent = parent;}else{parent->_right = cur;cur->_parent = parent;}// 2. 新节点插入后,AVL树的平衡性可能会遭到破坏,此时就需要更新平衡因子,并检测是否//破坏了AVL树的平衡性/*pCur插入后,pParent的平衡因子一定需要调整,在插入之前,pParent的平衡因子分为三种情况:-1,0, 1, 分以下两种情况:1. 如果pCur插入到pParent的左侧,只需给pParent的平衡因子-1即可2. 如果pCur插入到pParent的右侧,只需给pParent的平衡因子+1即可此时:pParent的平衡因子可能有三种情况:0,正负1, 正负21. 如果pParent的平衡因子为0,说明插入之前pParent的平衡因子为正负1,插入后被调整成0,此时满足
AVL树的性质,插入成功2. 如果pParent的平衡因子为正负1,说明插入前pParent的平衡因子一定为0,插入后被更新成正负1,此
时以pParent为根的树的高度增加,需要继续向上更新3. 如果pParent的平衡因子为正负2,则pParent的平衡因子违反平衡树的性质,需要对其进行旋转处理*///更新平衡因子while (parent){if (parent->_left == cur){parent->_bf--;}else{parent->_bf++;}if (parent->_bf == 0){//二叉树高度没问题,更新结束break;}else if (parent->_bf == 1 || parent->_bf == -1){// 插入前双亲的平衡因子是0,插入后双亲的平衡因子为1 或者 -1 ,说明以双亲为根的二叉树// 的高度增加了一层,因此需要继续向上调整cur = parent;parent = parent->_parent;}else if (parent->_bf == -2 || parent->_bf == 2){//双亲的平衡因子为正负2,违反了AVL树的平衡性//二叉树平衡被破坏,需要旋转完成平衡//判断是右单旋还是左单旋还是双旋//右单旋if (parent->_bf == -2 && cur->_bf == -1){//...}//左单旋else if (parent->_bf == 2 && cur->_bf == 1){//...}//左右双旋else if (parent->_bf == -2 && cur->_bf == 1){//...}//右左双旋else if (parent->_bf == 2 && cur->_bf == -1){//...}}else{//理论上不会出现这种状况assert(false);}}return true;
}

3.AVL树的旋转

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

/*上图在插入前,AVL树是平衡的,新节点插入到30的左子树(注意:此处不是左孩子)中,30左
子树增加了一层,导致以60为根的二叉树不平衡,要让60平衡,只能将60左子树的高度减少一层,右子
树增加一层,即将左子树往上提,这样60转下来,因为60比30大,只能将其放在30的右子树,而如果30有
右子树,右子树根的值一定大于30,小于60,只能将其放在60的左子树,旋转完成后,更新节点
的平衡因子即可。在旋转过程中,有以下几种情况需要考虑:1. 30节点的右孩子可能存在,也可能不存在2. 60可能是根节点,也可能是子树如果是根节点,旋转完成后,要更新根节点如果是子树,可能是某个节点的左子树,也可能是右子树此处可举一些详细的例子进行画图,考虑各种情况,加深旋转的理解
*/
void _RotateR(Node Parent)
{// SubL: Parent的左孩子// SubLR: Parent左孩子的右孩子,注意:该Node SubL = Parent->_Left;Node SubLR = SubL->_Right;// 旋转完成之后,30的右孩子作为双亲的左孩子Parent->_Left = SubLR;// 如果30的左孩子的右孩子存在,更新亲双亲if (SubLR)SubLR->_Parent = Parent;// 60 作为 30的右孩子SubL->_Right = Parent;// 因为60可能是棵子树,因此在更新其双亲前必须先保存60的双亲Node Parent = Parent->_Parent;// 更新60的双亲Parent->_Parent = SubL;// 更新30的双亲SubL->_Parent = Parent;// 如果60是根节点,根新指向根节点的指针if (NULL == Parent){_root = SubL;SubL->_Parent = NULL;}else{// 如果60是子树,可能是其双亲的左子树,也可能是右子树if (Parent->_Left == Parent)Parent->_Left = SubL;elseParent->_Right = SubL;}// 根据调整后的结构更新部分节点的平衡因子Parent->_bf = SubL->_bf = 0;
}

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

//左单旋
void LNode(Node* parent)
{/*if (parent == _root){Node* pparent = nullptr;}else{Node* pparent = parent->_parent;}*/Node* pparent = parent->_parent;Node* subR = parent->_right;Node* subRL = subR->_left;parent->_left = subRL;if (subRL)subRL->_parent = parent;subR->_left = parent;parent->_parent = subR;if (pparent){subR->_parent = pparent;if (pparent->_left = parent){pparent->_left = subR;}else{pparent->_right = subR;}}else{_root = subR;subR->_parent = nullptr;}parent->_bf = subR->_bf = 0;
}

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

将双旋变成单旋后再旋转,即: 先对 30 进行左单旋,然后再对 90 进行右单旋 ,旋转完成后再
考虑平衡因子的更新。
// 旋转之前,60的平衡因子可能是-1/0/1,旋转完成之后,根据情况对其他节点的平衡因子进
//行调整
void _RotateLR(Node Parent)
{Node SubL = Parent->_Left;Node SubLR = SubL->_Right;// 旋转之前,保存pSubLR的平衡因子,旋转完成之后,需要根据该平衡因子来调整其他节点的平衡因子int bf = SubLR->_bf;// 先对30进行左单旋_RotateL(Parent->_Left);// 再对90进行右单旋_RotateR(Parent);if (1 == bf)SubL->_bf = -1;else if (-1 == bf)Parent->_bf = 1;
}

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

//右左双旋
void RLNode(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RNode(parent->_right);LNode(parent);if (bf == 1){subRL->_bf = 0;subR->_bf = 0;parent->_bf = -1;}else if (bf == -1){subRL->_bf = 0;subR->_bf = 1;parent->_bf = 0;}else if (bf == 0){subRL->_bf = 0;subR->_bf = 0;parent->_bf = 0;}else{//理论没有该状况assert(false);}
}
总结:
假如以 Parent 为根的子树不平衡,即 Parent 的平衡因子为 2 或者 -2 ,分以下情况考虑
1)Parent 的平衡因子为 2 ,说明 Parent 的右子树高,设 Parent 的右子树的根为 SubR
SubR 的平衡因子为 1 时,执行左单旋
SubR 的平衡因子为 -1 时,执行右左双旋
2)Parent 的平衡因子为 -2 ,说明 Parent 的左子树高,设 Parent 的左子树的根为 SubL
SubL 的平衡因子为 -1 是,执行右单旋
SubL 的平衡因子为 1 时,执行左右双旋
旋转完成后,原 Parent 为根的子树个高度降低,已经平衡,不需要再向上更新。

4.AVL树的验证

AVL 树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证 AVL 树,可以分两步:
        1. 验证其为二叉搜索树
            如果中序遍历可得到一个有序的序列,就说明为二叉搜索树
        2. 验证其为平衡树
            ● 每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子 )
            ● 节点的平衡因子是否计算正确
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){return false;}//可以顺便再检查一下平衡因子if (abs(LeftHeight - RightHeight) != root->_bf){cout << root->_kv.first << endl;return false;}return _IsBalance(root->_left) && _IsBalance(root->_right);
}

三、AVL树的性能

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

四、完结撒❀

如果以上内容对你有帮助不妨点赞支持一下,以后还会分享更多编程知识,我们一起进步。
最后我想讲的是,据说点赞的都能找到漂亮女朋友❤

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

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

相关文章

容器化实践:DevOps环境下的容器交付流程

DevOps的兴起是为了应对市场和消费者对技术应用的不断增长的需求。它的目标是构建一个更快的开发环境&#xff0c;同时保持软件的高质量标准。DevOps还致力于在敏捷开发周期中提升软件的整体品质。这一目标的实现依赖于多种技术、平台和工具的综合运用。 结合容器化技术与DevO…

Xamarin.Android实现通知推送功能(1)

目录 1、背景说明1.1 开发环境1.2 实现效果1.2.1 推送的界面1.2.2 推送的设置1.2.3 推送的功能实现1.2.3.1、Activity的设置【重要】1.2.3.2、代码的实现 2、源码下载3、总结4、参考资料 1、背景说明 在App开发中&#xff0c;通知&#xff08;或消息&#xff09;的推送&#x…

jadx-gui-1.5 反编译工具使用教程 反混淆 Java android 查看签名

JADX&#xff1a;JADX是一个强大的反编译工具&#xff0c;它支持命令行和图形界面操作。除了基本的反编译功能外&#xff0c;JADX还提供了反混淆功能&#xff0c;有助于提高反编译后代码的可读性。 在Android开发和安全分析领域&#xff0c;反编译工具扮演着至关重要的角色。这…

HTML静态网页成品作业(HTML+CSS)—— 金宝贝儿童教育机构介绍网页(2个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;未使用Javacsript代码&#xff0c;共有2个页面。 二、作品演示 三、代…

【数据分享】中国高技术产业统计年鉴(2023年)

大家好&#xff01;今天我要向大家介绍一份重要的高技术产业发展情况统计数据资源——《中国高技术产业统计年鉴》。这份年鉴涵盖了从2023年中国高技术产业发展情况的全面数据&#xff0c;并以多格式提供免费下载。&#xff08;无需分享朋友圈即可获取&#xff09; 数据介绍 …

离散数学答疑 3

&#xff5e;A&#xff1a;A的补集 有时候空集是元素&#xff0c;有时候就是纯粹的空集 A-B的定义&#xff1a; 笛卡尔积&#xff1a; 求等价关系&#xff1a;先求划分再一一列举 不同划分&#xff1a;分几块。一块&#xff1a;两块&#xff1a;三块&#xff1a;分别计算 Ix是…

LeetCode62不同路径

题目描述 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。问总共有多少条不同的路径&#xff1f; …

【Spring Cloud Alibaba】开源组件Sentinel

目录 什么是Sentinel发展历史与Hystrix的异同 Sentinel可以做什么&#xff1f;Sentinel的功能Sentinel的开源生态Sentinel的用户安装Sentinel控制台预备环境准备Sentinel 分为两个部分:下载地址 项目集成Sentinel创建项目修改依赖信息添加启动注解添加配置信息在控制器类中新增…

Django 里的表格内容做修改

当Django里表格内容需要做修改&#xff0c;可以这么操作。 先看效果图 修改后的表格 1. 先得在 asset_list.html 里修改。你们的html有可能跟我不一样 <table border"1px"><thead><tr><th>ID</th><th>标题</th><th…

九种mfc140u.dll丢失的解决方法,全面解决mfc140u.dll文件丢失

mfc140u.dll是 Microsoft Visual C 2015 Redistributable 的一部分&#xff0c;它与 Microsoft 基础类库&#xff08;MFC&#xff09;的 Unicode 版本有关。当您在运行使用 Visual C 2015 开发的应用程序时&#xff0c;可能会碰到关于mfc140u.dll丢失的错误。下面列出了一些解决…

探索k8s集群的配置资源(secret和configmap)

目录 ConfigMap ConfigMap&#xff08;主要是将配置目录或者文件挂载到k8s里面使用&#xff09; 与Secret类似&#xff0c;区别在于ConfigMap保存的是不需要加密配置的信息。&#xff08;例如&#xff1a;配置文件&#xff09; ConfigMap 功能在 Kubernetes1.2 版本中引入&…

Python深度学习基于Tensorflow(13)目标检测实战

文章目录 RPN 整体代码RPN 具体实现过程数据标注读取标注数据固定图片大小调整目标框使用预训练模型获取 feature_shape定义 RPN 网络生成RPN 的 CLS 和 REG 数据集获取所有的锚点计算锚点与目标框的IOU 定义 RPN loss 和 训练过程 参考资料 这里实现的是二阶段目标检测&#x…

Linux系统下 安装 Nginx

一、下载Nginx安装包 压缩包下载地址&#xff1a;nginx: download 服务器有外网&#xff0c;可直接使用命令下载 wget -c https://nginx.org/download/nginx-1.24.0.tar.gz 二、安装Nginx 1、解压 tar -zxvf nginx-1.24.0.tar.gz 2、安装Nginx所需依赖 yum install -y gc…

线性代数|机器学习-P1课程简介

文章目录 1. 书籍下载2. 正文 1. 书籍下载 链接&#xff1a;https://pan.baidu.com/s/1QbK0enLh0x4nU1c4Tqwlkw 提取码&#xff1a;r7ft 本课程回顾线性代数在概率论、统计学、优化和深度学习中的应用。是GILBERT STRANG教授的有一个经典的课程。课程将线性代数分为如下部分&a…

【第二节】C/C++数据结构之线性表

目录 一、线性表基本说明 1.1 基本概念 1.2 抽象数据类型 1.3 存储结构 1.4 插入与删除的区别 1.5 顺序存储和链式存储的优缺点 二、链表 2.1 基本概念 2.2 抽象数据类型 2.3 单链表的定义 2.4 单链表的基本操作 2.5 单链表模板形式的类定义与实现 三、单向循环链…

探索未来制造,BFT Robotics引领潮流

“买机器人&#xff0c;上BFT” 在这个快速变化的时代&#xff0c;创新和效率是企业发展的关键。BFT Robotics&#xff0c;作为您值得信赖的合作伙伴&#xff0c;专注于为您提供一站式的机器人采购和自动化解决方案。 产品系列&#xff1a; 协作机器人&#xff1a;安全、灵活、…

OpenShift 4 - OpenShift Service Mesh 3 预览

《OpenShift / RHEL / DevSecOps 汇总目录》 了解 OpenShift Service Mesh 3 的变化 OpenShift Service Mesh 是一套在 OpenShift 上安装部署、跟踪监控 Istio 运行环境的实现。红帽在 2023 年底推出了技术预览版的 OpenShift Service Mesh 3&#xff0c;它和目前的 OpenShif…

RERCS系统开发实战案例-Part01 快速启动面板创建新功能启动面板

需求背景&#xff1a;RERCS系统设计合同应收付比例调整界面&#xff0c;目的为合同与应收付款调整关联&#xff0c;保证数据的完整性与准确性。 步骤① 参数化快速启动板事务码 &#xff1a;LPD_CUST_PARAM 选择对应的角色与实例 可以看到系统中的快速启动面板菜单中已有的功能…

顶顶通呼叫中心中间件-asr录音路径修改(mod_cti基于FreeSWITCH)

顶顶通呼叫中心中间件-asr录音路径修改(mod_cti基于FreeSWITCH) 录音路径模板。如果不是绝对路径&#xff0c;会把这个路径追加到FreeSWITCH的recordings后面。支持变量&#xff0c;比如日期 ${strftime(%Y-%m-%d)}。最后一个录音文件路径会保存到变量 ${cti_asr_last_record_…

最大矩形问题

柱状图中最大的矩形 题目 分析 矩形的面积等于宽乘以高&#xff0c;因此只要能确定每个矩形的宽和高&#xff0c;就能计算它的面积。如果直方图中一个矩形从下标为 i 的柱子开始&#xff0c;到下标为 j 的柱子结束&#xff0c;那么这两根柱子之间的矩形&#xff08;含两端的柱…