【C++深度探索】深入解析AVL树的底层实现机制

🔥 个人主页:大耳朵土土垚
🔥 所属专栏:C++从入门至进阶

这里将会不定期更新有关C/C++的内容,欢迎大家点赞,收藏,评论🥳🥳🎉🎉🎉

前言

  AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树.一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:它的左右子树都是AVL树,左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1).接下来我们继续学习AVL树底层实现的部分机制.

文章目录

  • 前言
  • 1.AVL树结构
  • 2.AVL树的插入
    • ✨左单旋
    • ✨右单旋
    • ✨右左双旋
    • ✨左右双旋
  • 3.中序遍历
  • 4. AVL树的验证
  • 5.验证用例
  • 6.结语

1.AVL树结构

//AVL树节点类
template<class K, class V>
struct AVLTreeNode
{pair<K, V> _kv;AVLTreeNode<K, V>* _pLeft;AVLTreeNode<K, V>* _pRight;AVLTreeNode<K, V>* _pParent;int _bf; // balance factorAVLTreeNode(const pair<K, V>& kv):_kv(kv), _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr), _bf(0){}
};// AVL: 二叉搜索树 + 平衡因子的限制
template<class K,class V>
class AVLTree
{typedef AVLTreeNode<K,V> Node;
public:AVLTree(): _pRoot(nullptr){}// 在AVL树中插入值为kv的节点bool Insert(const pair<K, V>& kv);//中序遍历void InOrder(){_InOrder(_pRoot);}//判断是否是平衡树bool IsBalanceTree(){//嵌套一层函数return _IsBalanceTree(_pRoot);}private:bool _IsBalanceTree(Node* pRoot);int _Height(Node* pRoot);void _InOrder(Node* root);// 右单旋void RotateR(Node* parent);// 左单旋void RotateL(Node* parent);// 右左双旋void RotateRL(Node* parent);// 左右双旋void RotateLR(Node* parent)

2.AVL树的插入


AVL树的插入过程可以分为两步:

  • 按照二叉搜索树的方式插入新节点
  • 调整节点的平衡因子

在插入新节点之后,AVL树的平衡性可能会遭到破坏,此时就需要更新平衡因子,并检测是否破坏了AVL树的平衡性。

如下图所示:

pCur插入后,pParent的平衡因子一定需要调整,在插入之前,pParent
的平衡因子分为三种情况:-1,0, 1, 分以下两种情况:

  • 如果pCur插入到pParent的左侧,只需给pParent的平衡因子-1即可,如上图所示
  • 如果pCur插入到pParent的右侧,只需给pParent的平衡因子+1即可

此时,pParent的平衡因子也可能有三种情况:0,正负1, 正负2

  • 如果pParent的平衡因子为0,说明插入之前pParent的平衡因子为正负1,插入后被调整成0,此时满足AVL树的性质,插入成功,如上图所示;
  • 如果pParent的平衡因子为正负1,说明插入前pParent的平衡因子一定为0(不可能是2,因为这样没插入新节点前该树就已经不平衡了),插入后被更新成正负1,此时以pParent为根的树的高度增加,需要继续向上更新,如下图所示:

AVL树插入新节点90之后,pParent也就是80节点的平衡因子就需要更新为1,继续往上更新,直到60节点的平衡因子被更新为2,说明不符合AVL树的性质,就需要进行旋转来维持平衡。

  • 如果更新后pParent的平衡因子为正负2,则pParent的平衡因子违反平衡树的性质,需要对其进行旋转处理,如上图所示

所以对于AVL树插入新节点来说,我们需要更新插入后由于左右子树高度差改变带来的新的平衡因子,然后根据平衡因子是否大于1或小于-1来判断AVL树是否平衡,如果不平衡我们就必须通过旋转来维持平衡,代码如下:

	// 在AVL树中插入值为kv的节点bool Insert(const pair<K, V>& kv){//1.先构造新节点Node* newnode = new Node(kv);Node* cur = _pRoot;//2.判断插入位置if (cur == nullptr){//如果树为空_pRoot = newnode;return true;}//如果AVL树不为空,找到插入位置Node* parent = cur->_pParent;while (cur){	if (cur->_kv.first > kv.first){parent = cur;cur = cur->_pLeft;}else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_pRight;}//找到相同节点,AVL树不能插入相同节点else{return false;}}//3.插入节点//先判断是插入左边还是右边if (parent->_kv.first > kv.first){//插入左边parent->_pLeft = newnode;		}else{//插入右边parent->_pRight = newnode;}newnode->_pParent = parent;cur = newnode;//4.更新平衡因子while (parent){if (parent->_pLeft == cur)parent->_bf--;elseparent->_bf++;// 更新后检测双亲的平衡因子if (parent->_bf == 0){break;}else if (parent->_bf == 1 || parent->_bf == -1){//平衡因子需要继续往上更新cur = parent;parent = parent->_pParent;}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//(parent->_bf == -2 && cur->_bf == 1){RotateLR(parent);}break;}else{//其他情况,断言报错assert(false);}}return true;}

这里要注意AVL树不能插入相同节点

AVL树插入新节点的逻辑结构如上述代码所示,如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:那么我们具体来看看AVL树旋转的实现:

✨左单旋

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


parent和cur的平衡因子经过旋转之后变为0,维持了AVL树的平衡。

代码如下:

// 左单旋
void RotateL(Node* parent)
{Node* cur = parent->_pRight;//将cur的左边给parent的右边,cur的左边再指向parentparent->_pRight = cur->_pLeft;cur->_pLeft = parent;//链接cur与parent的父节点if (parent->_pParent == nullptr){//如果pParent是根节点cur->_pParent = nullptr;_pRoot = cur;}else if (parent->_pParent->_pLeft == parent)parent->_pParent->_pLeft = cur;elseparent->_pParent->_pRight = cur;//更新父节点cur->_pParent = parent->_pParent;parent->_pParent = cur;if(parent->_pRight)//判断pParent的右边是否存在parent->_pRight->_pParent = parent;//更新平衡因子parent->_bf = 0;cur->_bf = 0;
}

✨右单旋

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


代码如下:
	// 右单旋void RotateR(Node* parent){Node* cur = parent->_pLeft;//将cur的右边给pParent的左边,cur的右边再指向pParentparent->_pLeft = cur->_pRight;cur->_pRight = parent;//链接cur与pParent的父节点if (parent->_pParent == nullptr){//如果pParent是根节点cur->_pParent = nullptr;_pRoot = cur;}else if (parent->_pParent->_pLeft == parent)parent->_pParent->_pLeft = cur;elseparent->_pParent->_pRight = cur;//更新父节点cur->_pParent = parent->_pParent;parent->_pParent = cur;if (parent->_pLeft)parent->_pLeft->_pParent = parent;//更新平衡因子parent->_bf = 0;cur->_bf = 0;}

✨右左双旋

新节点插入较高右子树的左侧—右左:先右单旋再左单旋,借助上面实现的右单旋和左单旋即可

如下图所示,较高右子树(以cur节点为根节点的树)的左侧(以child节点为根节点的树),插入节点,注意这里可以插入child的左侧或右侧,只要插入在child的子树上即可,所以可以是下图中的b或c,这里选择b:


前文我们说过只要插入在child的子树上即可,所以可以是上图中的b或c,这里选择b,那么如果是c的话,还是需要进行左右双旋,与选b的区别在于平衡因子的不同,这里可以根据具体选择分析出来,所以在双旋之后记得根据不同的插入位置更新不同的平衡因子。

代码如下:

// 右左双旋
void RotateRL(Node* parent)
{Node* cur = parent->_pRight;Node* child = cur->_pLeft;//旋转前保存child的平衡因子int bf = child->_bf;//cur的左边高,先右旋RotateR(cur);//再左旋RotateL(parent);//根据不同插入位置更新不同的平衡因子if (bf == -1)//插入在b{cur->_bf = 1;}else if (bf == 1)//插入在c{parent->_bf = -1;}
}

✨左右双旋

新节点插入较高左子树的右侧—左右:先左单旋再右单旋,借助上面实现的右单旋和左单旋即可

如下图所示,左右双旋与右左双旋类似,也可以插入在下图中的b或从,旋转方式一样,不影响,就是最后平衡因子需要根据插入的位置更新:


代码如下:

// 左右双旋
void RotateLR(Node* parent)
{Node* cur = parent->_pLeft;Node* child = cur->_pRight;//旋转前保存child的平衡因子int bf = child->_bf;//cur的右边高,先左旋RotateL(cur);//再右旋RotateR(parent);//根据不同插入位置更新不同的平衡因子if (bf == -1)//b{parent->_bf = 1;}else if (bf == 1)//c{cur->_bf = -1;}
}

小结:

假如以parent为根的子树不平衡,即parent的平衡因子为2或者-2,分以下情况考虑

  1. parent的平衡因子为2,说明parent的右子树高,设parent的右子树的根为cur
    当cur的平衡因子为1时,执行左单旋
    当cur的平衡因子为-1时,执行右左双旋
  2. parent的平衡因子为-2,说明parent的左子树高,设parent的左子树的根为cur
    当cur的平衡因子为-1是,执行右单旋
    当cur的平衡因子为1时,执行左右双旋

旋转完成后,原parent为根的子树个高度降低,已经平衡,不需要再向上更新。

3.中序遍历

  AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树,其中序遍历和我们之前实现过的二叉搜索树一样。

代码如下:

//中序遍历
void InOrder()
{_InOrder(_pRoot);
}void _InOrder(Node* root)
{if (root == nullptr){return;}_InOrder(root->_pLeft);cout << root->_kv.first << ":" << root->_kv.second << endl;_InOrder(root->_pRight);}

4. AVL树的验证

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

  • 验证其是否为二叉搜索树:
    如果中序遍历可得到一个有序的序列,就说明为二叉搜索树
  • 验证其是否为平衡树:
    每个节点子树高度差的绝对值不超过1
    对于验证是否是平衡树,代码如下:
bool IsBalanceTree()
{
//嵌套一层函数return _IsBalanceTree(_pRoot);
}
bool _IsBalanceTree(Node* pRoot)
{// 空树也是AVL树if (nullptr == pRoot) return true;// 计算pRoot节点的平衡因子:即pRoot左右子树的高度差int leftHeight = _Height(pRoot->_pLeft);int rightHeight = _Height(pRoot->_pRight);int diff = rightHeight - leftHeight;// 如果计算出的平衡因子与pRoot的平衡因子不相等,或者pRoot平衡因子的绝对值超过1,则一定不是AVL树if (diff != pRoot->_bf || (diff > 1 || diff < -1))return false;// pRoot的左和右如果都是AVL树,则该树一定是AVL树return _IsBalanceTree(pRoot->_pLeft) && _IsBalanceTree(pRoot -> _pRight);
}//求树的高度
size_t _Height(Node* pRoot)
{if (pRoot == nullptr)return 0;size_t left = _Height(pRoot->_pLeft);size_t right = _Height(pRoot->_pRight);return (left >= right ? left : right) + 1;
}

计算pRoot节点的平衡因子:即计算pRoot左右子树的高度差,我们利用递归实现即可。计算是否为平衡树因为是递归需要传递根节点,但是我们在使用时并不能获取根节点,所以需要嵌套一层函数。

5.验证用例

void TestAVLTree()
{AVLTree<int, int> t;int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };//用例1//int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16,14 };//用例2for (auto e : a){t.Insert({ e, e });}cout <<"是否是平衡树:"<< t.IsBalanceTree() << endl;t.InOrder();}

结果如下:



6.结语

  因为AVL树也是二叉搜索树,其他的类似查找节点,析构函数和构造函数都与二叉搜索树类似,对于删除节点,可按照二叉搜索树的方式将节点删除,然后再更新平衡因子,最差情况下一直要调整到根节点的位置,大家有兴趣可以自己查找了解一下,以上就是今天所有的内容啦~ 完结撒花 ~🥳🎉🎉

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

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

相关文章

阿里云 服务器安装rabbit

现在我们去服务器安装一个rabbit 进入home 创建一个rabbit文件夹 /home/rabbit vim deployRabbit.sh 脚本内容 #!/bin/bash docker run -d \ --name dev.rabbit \ --network dev-net \ -p 15672:15672 \ -v ./data:/var/lib/rabbitmq \ --hostname dev.rabbit \ rabbitmq:…

反射和游戏场景

主要内容 1.Unity中的反射机制运用 2.游戏场景本质 回顾一下反射的概念 程序正在运行时没有查看其它程序集或者自身的元数据&#xff0c;一个运行的程序查看本身或者其它程勋的元数据的行为就叫做反射。 在程序运行时&#xff0c;通过反射可以得到其它程序集或者自己程序集…

UCOS-III 互斥锁接口详解

在实时操作系统uC/OS-III中&#xff0c;互斥锁&#xff08;Mutex&#xff09;是一种用于管理对共享资源的访问的同步机制。互斥锁通过保证在任何时刻只有一个任务可以持有锁&#xff0c;从而防止资源竞争问题。同时&#xff0c;uC/OS-III还实现了递归锁定和优先级继承机制&…

2024杭电多校第三场

目录 1001-深度自同构 1003-游走 1007-单峰数列 1008-比特跳跃 1011-抓拍 1012-死亡之组 1001-深度自同构 每个数的答案其实与它的各个因数有关&#xff0c;正向递推一下 #include <bits/stdc.h> using namespace std; #define int long long const int N1e65; co…

51 单片机的Keil5软件

1. KEIL C51 软件获取 博主网盘下载&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1YBfrRh2L7SIehS5xLQkAow?pwd4211 提取码&#xff1a;4211 也可以在 KEIL 的官网上下载&#xff1a;http:// https://www.keil.com/download/product/ 打开界面如下图所示&#xff1…

读零信任网络:在不可信网络中构建安全系统04最小特权

1. 公钥基础设施 1.1. PKI 1.2. 数字证书本身并不能解决身份认证问题 1.2.1. 需要一种方法来验证获得的公钥的确属于某人 1.2.2. 公钥基础设施&#xff08;PKI&#xff09;解决了这个问题 1.3. PKI定义了一组角色及其职责&#xff0c;能够在不可信的网络中安全地分发和验证…

AMQP-核心概念-终章

本文参考以下链接摘录翻译&#xff1a; https://www.rabbitmq.com/tutorials/amqp-concepts 连接&#xff08;Connections&#xff09; AMQP 0-9-1连接通常是长期保持的。AMQP 0-9-1是一个应用级别的协议&#xff0c;它使用TCP来实现可靠传输。连接使用认证且可以使用TLS保护…

观远BI经验总结

观远BI经验总结 观远BI&#xff08;Galaxy platform&#xff09;简介 ​ 观远数据是一站式智能分析平台&#xff0c;为企业提供数据分析可视化与智能决策服务&#xff0c;打通数据采集-数据接入-数据管理-数据开发-数据分析-AI建模-AI模型运行-数据应用全流程&#xff0c;全方…

Golang | Leetcode Golang题解之第300题最长递增子序列

题目&#xff1a; 题解&#xff1a; func lengthOfLIS(nums []int) int {if len(nums)<1{return len(nums)}dp : make([]int,len(nums))for i:0;i<len(nums);i{dp[i]1}res : 1for i:1;i<len(nums);i{for j:0;j<i;j{if nums[i] > nums[j]{dp[i] max(dp[i],dp[j…

录制创意无限的视频:2024年热门免费录屏软件精选

录屏会帮助我们捕捉屏幕上每一帧的精彩瞬间&#xff0c;不论是直播还是学习甚至是工作的会议都能用到这个功能。如果找到一款好用的免费录屏软件&#xff0c;那我们录屏时候会更随意&#xff0c;更愉悦一些吧。 1.福昕录屏大师 链接&#xff1a;www.foxitsoftware.cn/REC/ 这款…

【扒代码】X = output[:,:,y1:y2,x1:x2].sum()

假设我们有以下输入&#xff1a; output 是一个形状为 (1【batch size】, 1【channel】, 10, 10) 的张量&#xff0c;表示一个 10x10 的输出图像。boxes 是一个形状为 (1【index】, 2, 5) 的张量&#xff0c;表示两个边界框&#xff0c;每个边界框包含 5 个值 [index, y1, x1,…

AttributeError: ‘str‘ object has no attribute ‘decode‘

AttributeError: ‘str‘ object has no attribute ‘decode‘ 目录 AttributeError: ‘str‘ object has no attribute ‘decode‘ 【常见模块错误】 【解决方案】 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英杰&#x…

springBoot 3.X整合camunda

camunDa camunDa 是2013年从Activiti5 中分离出来的一个新的工作流引擎。Camunda 官方提供了 Camunda Platform、Camunda Modeler&#xff0c;其中 Camunda Platform 以 Camunda engine 为基础为用户提供可视化界面&#xff0c;Camunda Modeler 是流程文件建模平台&#xff0c…

虚拟换装的一个项目:IMAGDressing-v1

虚拟换装的一个项目&#xff1a;IMAGDressing-v1 IMAGDressing-v1是一个可定制虚拟着装系统&#xff0c;可以生成逼真的服装并支持场景编辑 特点&#xff1a; 1、支持服装生成&#xff0c;可根据&#xff0c;例如文本提示、图像、姿势等&#xff0c;生成逼真的服装图像&…

GuLi商城-商品服务-API-平台属性-规格参数新增与VO

重写保存方法: @Transactional(rollbackFor = Exception.class) @Ove

Linux驱动编程 - 字符设备驱动

目录 简介&#xff1a; 一、字符设备驱动框架 1、字符设备驱动入口 2、字符设备驱动加载过程 2.1 申请设备号 2.1.1 分配设备号函数 (1) 静态分配函数 (2) 动态分配函数 (3) 注销设备号 2.1.2 设备号中的主/次设备号 2.1.3 申请设备号示例 2.2 注册字符设备 2.2.1 cd…

手摸手教你撕碎西门子S7通讯协议06--S7Read读取short数据

1、S7通讯回顾 - &#xff08;1&#xff09;建立TCP连接 Socket.Connect-》已实现 - &#xff08;2&#xff09;发送访问请求 COTP-》已实现 - &#xff08;3&#xff09;交换通信信息 Setup Communication-》已实现 - &#xff08;4&#xff09;执行相关操作 …

如何使用rdma-core来实现RDMA操作

rdma-core 是一个开源项目&#xff0c;为远程直接内存访问&#xff08;RDMA&#xff09;提供用户空间的支持。它包括 RDMA 设备的驱动程序、库和工具&#xff0c;旨在简化 RDMA 应用的开发和部署。 基础知识参考博文&#xff1a; 一文带你了解什么是RDMA RDMA 高性能架构基本…

Langchain--如何使用大模型 2.0

【&#x1f34a;易编橙终身成长社群&#x1f34a;】 大家好&#xff0c;我是小森( &#xfe61;ˆoˆ&#xfe61; ) &#xff01; 易编橙终身成长社群创始团队嘉宾&#xff0c;橙似锦计划领衔成员、阿里云专家博主、腾讯云内容共创官、CSDN人工智能领域优质创作者 。 Langch…

【已解决】嵌入式linux mobaxterm unable to open connection to comx 串口正常连接,但终端无法输入

1.点击Session重新选择串口&#xff0c;注意看看串口是不是连接到虚拟机&#xff0c;导致串口被占用。 2.选择PC机与开发板连接的串口&#xff0c;不知道的话可以打开设备管理器看看&#xff0c;选择正确的波特率&#xff0c;一般是115200。 3.关键一步&#xff1a;选择后别急…