如何在AVL树中高效插入并保持平衡:一步步掌握旋转与平衡因子 —— 旋转篇

devtools/2025/3/16 11:18:31/

文章目录

  • AVL树种旋转的规则
  • 右单旋
  • 右单旋代码
  • 左单旋
  • 左单旋代码
  • 左右双旋
  • 左右单旋的代码
  • 右左单旋
  • 右左单旋的代码

AVL树种旋转的规则

在AVL树中,旋转是为了保持树的平衡性。AVL树是一种自平衡的二叉搜索树,它要求每个节点的左右子树的高度差不能超过1。当插入或删除节点后,可能会导致树的平衡因子(左右子树的高度差)超出允许的范围(-1、0、1)。为了恢复平衡,AVL树会使用旋转来调整树的结构。

旋转类型
根据不平衡的类型,AVL树有四种基本的旋转方式:

  • 单右旋转(Right Rotation) - 适用于左子树过高的情况。
  • 单左旋转(Left Rotation) - 适用于右子树过高的情况。
  • 左-右双旋转(Left-Right Rotation) - 适用于左子树的右子树过高的情况。
  • 右-左双旋转(Right-Left Rotation) - 适用于右子树的左子树过高的情况。

在上一篇文章当中,我们已经知道了关于AVL树的结构
下面重新展示出来:

template<class K, class V>
struct AVLTreeNode 
{// 每个节点包含的元素和指针pair<K, V> _kv;           // 存储键值对AVLTreeNode<K, V>* _left;  // 左子树指针AVLTreeNode<K, V>* _right; // 右子树指针AVLTreeNode<K, V>* _parent; // 父节点指针int _bf;                   // 平衡因子// 构造函数AVLTreeNode(const pair<K, V>& kv): _kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _bf(0){}
};template<class K, class V>
class AVLTree 
{typedef AVLTreeNode<K, V> Node;  // 节点类型
public:// 其他操作(插入、删除、查找等)可以在此添加private:Node* _root;  // 根节点指针
};

右单旋

抽象的来看右单旋的图(我们可以在最后在看这个图,先有一个大概印象
在这里插入图片描述

  1. 最基础的情况
    在这里插入图片描述

我们肉眼看到的是在 5 结点插入了一个左子树 1 结点,原先平衡的树,在新插入一个节点之后就不平衡了,其grandfather的平衡因子由-1变为了-2, 不满足AVL树的平衡条件,所以这种情况下,我们就需要通过旋转,以此达到让插入之后形成的这颗树平衡,可以看见,这颗树是左边高,所以需要向右进行旋转,即左高往右旋转,所以旋转之后的图形即是上图的最右侧图形,而且,5 变成了 10的父节点,10变成了 5 的右子树

  1. 情况1上增加一个高度的情况
    在这里插入图片描述

同样的,在左子树叶子结点插入左子树,也是通过右旋转,以此达到平衡

  1. 再次添加难度
    在这里插入图片描述

  2. 最复杂的情况
    在这里插入图片描述


在这里插入图片描述

右单旋代码

根据上面图片理解下面的代码

void RotateR(Node* parent)
{// 获取父节点的左子节点Node* subL = parent->_left;// 获取左子节点的右子节点Node* subLR = subL->_right;// 修改父节点的左子节点为 subLRparent->_left = subLR;// 如果 subLR 不为空,更新 subLR 的父节点为 parentif (subLR)subLR->_parent = parent;// 获取 parent 的父节点,方便之后修改,这个地方因为parent可能不是根节点,所以可能会继续向上进行平衡调整Node* parentParent = parent->_parent;// 将左子节点 subL 的右子节点指向 parent,parent 的父节点指向 subLsubL->_right = parent;parent->_parent = subL;// 如果 parent 是根节点,则需要修改树的根// 如果 parent 是子树的一部分,则只需修改父节点的指向if (parentParent == nullptr){// 如果 parent 没有父节点(即 parent 是树的根节点),修改根节点_root = subL;subL->_parent = nullptr;  // subL 的父节点应为 null}else{// 如果 parent 有父节点(即 parent 只是子树的一部分),更新父节点的左/右指针if (parent == parentParent->_left){parentParent->_left = subL;}else{parentParent->_right = subL;}// 更新 subL 的父节点为 parent 的父节点subL->_parent = parentParent;}// 将 parent 和 subL 的平衡因子都设置为 0// 因为旋转后,两个节点的平衡因子应该都变为 0parent->_bf = subL->_bf = 0;
}

衷心希望读者,可以拿起笔来画图进行思考

左单旋

在这里插入图片描述

在分析完右单旋之后,左单旋的思考过程也是如此,当你弄清楚右单旋的过程,左单旋就是轻轻松松,举一反三!!!

左单旋代码

void RotateL(Node* parent)
{// 获取父节点的右子节点Node* subR = parent->_right;// 获取右子节点的左子节点Node* subRL = subR->_left;// 修改父节点的右子节点为 subRLparent->_right = subRL;// 如果 subRL 不为空,更新 subRL 的父节点为 parentif(subRL)subRL->_parent = parent;// 获取 parent 的父节点,方便之后修改Node* parentParent = parent->_parent;// 将右子节点 subR 的左子节点指向 parent,parent 的父节点指向 subRsubR->_left = parent;parent->_parent = subR;// 如果 parent 是根节点,则需要修改树的根// 如果 parent 是子树的一部分,则只需修改父节点的指向if (parentParent == nullptr){// 如果 parent 没有父节点(即 parent 是树的根节点),修改根节点_root = subR;subR->_parent = nullptr;  // subR 的父节点应为 null}else{// 如果 parent 有父节点(即 parent 只是子树的一部分),更新父节点的左/右指针if (parent == parentParent->_left){parentParent->_left = subR;}else{parentParent->_right = subR;}// 更新 subR 的父节点为 parent 的父节点subR->_parent = parentParent;}// 将 parent 和 subR 的平衡因子都设置为 0// 因为旋转后,两个节点的平衡因子应该都变为 0parent->_bf = subR->_bf = 0;
}

左右双旋

左右双旋的情况——即左边高(子树在右子树),所以单独一次左旋或者右旋都是无法处理的,所以需要两者配合进行,才能让平衡因子平衡。

  1. 最基础情况
    在这里插入图片描述

  1. 情况1增加一些难度
    在这里插入图片描述

3.抽象的描述
在这里插入图片描述

左右单旋的代码

void RotateLR(Node* parent)
{// 获取父节点的左子节点Node* subL = parent->_left;// 获取左子节点的右子节点Node* subLR = subL->_right;// 获取 subLR 的平衡因子,用于调整旋转后的平衡因子int bf = subLR->_bf;// 先对左子节点进行左单旋 (RotateL)RotateL(parent->_left);// 然后对 parent 进行右单旋 (RotateR)RotateR(parent);// 根据 subLR 的平衡因子调整旋转后节点的平衡因子if (bf == 0){// 如果 subLR 的平衡因子是 0,旋转后所有节点的平衡因子都设置为 0subL->_bf = 0;subLR->_bf = 0;parent->_bf = 0;}else if (bf == -1){// 如果 subLR 的平衡因子是 -1,调整父节点、子节点的平衡因子subL->_bf = 0;subLR->_bf = 0;parent->_bf = 1;}else if (bf == 1){// 如果 subLR 的平衡因子是 1,调整父节点、子节点的平衡因子subL->_bf = -1;subLR->_bf = 0;parent->_bf = 0;}else{// 处理无效的平衡因子,理应不可能出现assert(false);}
}

右左单旋

在这里插入图片描述

右左单旋的代码

void RotateRL(Node* parent)
{// 获取父节点的右子节点Node* subR = parent->_right;// 获取右子节点的左子节点Node* subRL = subR->_left;// 获取 subRL 的平衡因子,用于调整旋转后的平衡因子int bf = subRL->_bf;// 先对右子节点进行右单旋 (RotateR)RotateR(parent->_right);// 然后对 parent 进行左单旋 (RotateL)RotateL(parent);// 根据 subRL 的平衡因子调整旋转后节点的平衡因子if (bf == 0){// 如果 subRL 的平衡因子是 0,旋转后所有节点的平衡因子都设置为 0subR->_bf = 0;subRL->_bf = 0;parent->_bf = 0;}else if (bf == 1){// 如果 subRL 的平衡因子是 1,调整父节点、子节点的平衡因子subR->_bf = 0;subRL->_bf = 0;parent->_bf = -1;}else if (bf == -1){// 如果 subRL 的平衡因子是 -1,调整父节点、子节点的平衡因子subR->_bf = 1;subRL->_bf = 0;parent->_bf = 0;}else{// 处理无效的平衡因子,理应不可能出现assert(false);}
}

http://www.ppmy.cn/devtools/167542.html

相关文章

OpenFeign的配置类可以进行哪些配置

1. 日志级别&#xff08;Logger Level&#xff09; 工作原理 Feign的日志级别控制了日志输出的详细程度&#xff0c;有助于调试和监控。日志级别包括&#xff1a; NONE&#xff1a;不记录任何信息。BASIC&#xff1a;仅记录请求方法和URL及响应状态码和执行时间。HEADERS&am…

Excel表一键查询工具

Excel表格里面存放的数据文件太多&#xff0c;显得杂乱无章&#xff0c;无论是进行搜索还是定位特定数据&#xff0c;都变得异常繁琐且效率低下。为了改善这一状况&#xff0c;今天特意给大家推荐一款既轻便又实用的excel查询小工具&#xff0c;其软件包体积不到4M&#xff0c;…

梧桐:开发者的命令行效率应用

为什么需要梧桐 正如梧桐的readme文档所言&#xff0c;在开发过程中&#xff0c;数据的编码与转换是开发者频繁面临的任务之一。例如&#xff0c;将字符串转换为Base64编码用于网络传输&#xff0c;或者将数字转换为二进制格式以进行底层操作。这些任务虽然简单&#xff0c;但…

windows更改系统时间后屏幕开始闪烁

将电脑时间手动调整到2017年后电脑屏幕一直狂闪不停&#xff0c; 进入任务管理器 1、按下winr键打开运行对话框&#xff0c;输入“regedit”回车。 2、打开注册表之后&#xff0c;定位到以下的位置&#xff1a;【计算机\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control…

移远通信联合德壹发布全球首款搭载端侧大模型的AI具身理疗机器人

在汹涌澎湃的人工智能浪潮中&#xff0c;具身智能正从实验室构想迈向现实应用。移远通信凭借突破性的端侧AI整体解决方案&#xff0c;为AI机器人强势赋能&#xff0c;助力其实现跨行业拓展&#xff0c;从工业制造到服务接待&#xff0c;再到医疗康养&#xff0c;不断改写各行业…

第八节:红黑树(初阶)

【本节要点】 红黑树概念红黑树性质红黑树结点定义红黑树结构红黑树插入操作的分析 一、红黑树的概念与性质 1.1 红黑树的概念 红黑树 &#xff0c;是一种 二叉搜索树 &#xff0c;但 在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是 Red和 Black 。 通过对 任何…

Flutter三棵树是什么,为什么这么设计

目录 1. 三棵树的定义与职责 (1) Widget 树 (2) Element 树 (3) RenderObject 树 2. 三棵树的协同工作流程 3. 为什么设计三棵树&#xff1f; (1) 性能优化 (2) 逻辑解耦 (3) 灵活性 4. 三棵树的设计优势总结 示例&#xff1a;动态列表更新 常见面试追问 Flutter 的…

【gopher的java学习笔记】如何知道一个jar包对应的maven中的groupId和atrifactId

java程序常见的一个错误之一就是通过Class类的方法&#xff08;比如Class.forName&#xff09;的时候会抛出ClassNotFoundException&#xff0c;要排查这个异常&#xff0c;就需要确认我们到底有没有这个Class。但是当我排查的时候&#xff0c;我发现我能排查的只是我的jar包里…