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

C++ : AVL 树之 右左双旋(第四章)

AVL树最后一章啦,其实我已经将红黑树的博客也写出来咯,而且这次添加了很多代码注释,让你直接独代码也可以自己慢慢分析的哦

一、什么是 RL 双旋?解决什么问题?

RL 双旋是处理 RL 失衡 的组合操作 —— 当某节点的右子树高度比左子树高 2(父节点 bf=2),但右孩子的左子树更高(右孩子 bf=-1)时,直接对父节点做左单旋无法平衡,需要先对右孩子做右单旋,将结构 “掰直” 为 RR 失衡,再对原父节点做左单旋,最终实现树的平衡。

简单说:右子树呈现 “先右后左拐”(RL)的形态,先把 “拐弯处” 的节点(subRL)提上来,将不规则的 RL 结构转化为规则的 RR 结构,再用左单旋完成平衡。

二、RL 失衡场景特征

要准确识别 RL 失衡,需抓住 父节点、右孩子、拐弯节点 的三个核心特征,结合具体例子理解更直观:

1. 场景示例(节点结构)

假设插入元素后,树的局部结构如下(括号内为节点 bf 值):

  • 父节点 A(6, bf=2):右子树比左子树高 2,触发失衡判断;
  • 右孩子 B(8, bf=-1):A 的右子树,但其左子树比右子树高 1(拐弯的根源);
  • 拐弯节点 C(7, bf=0):B 的左子树(subRL),插入点在 C 的子树中(如 C 的左孩子 5、右孩子 9);
  • 其他节点:A 的左子树 D(4, bf=0),B 的右子树 E(9, bf=0)

结构示意图(失衡前):

        A(6, bf=2)/   \
D(4,0)      B(8, bf=-1)/   \C(7,0)    E(9,0)/
F(5,0)  (插入点,导致C的左子树过高)

2. 核心特征(判断依据)

  1. 父节点 bf=2:右子树高度比左子树高 2,满足失衡触发条件;
  2. 右孩子 bf=-1:父节点的右子树 “向左拐”(左子树更高),而非 “纯右偏”;
  3. 失衡根源:右孩子的左子树(subRL)过高,直接左单旋会导致 subRL 的子树 “挂错父节点”,无法修复高度差。

三、RL 双旋步骤拆解

RL 双旋与 LR 双旋逻辑对称,同样分 “两步走”:先旋右孩子(掰直结构),再旋父节点(平衡树)。每一步都需明确节点的父子关系变化,避免指针悬空。

步骤 1:对右孩子(B)做右单旋(转化为 RR 结构)

目标:将拐弯节点 C(7) 提为右孩子 B(8) 的父节点,把 RL 结构转化为 RR 结构(纯右偏)。

具体操作(对应代码 RotateR(subR)):
  1. 取右孩子 B 的左子树 C(subRL),记录 C 的右子树(若有,如 C->_right);
  2. 将 C 的右子树交给 B 当左子树(若 C->_right 非空,需更新其 _parent 为 B);
  3. 将 B 降为 C 的右孩子,更新 B->_parent 为 C
  4. 将 C 提为原父节点 A 的右孩子,更新 C->_parent 为 A

步骤 1 后结构(RR 结构)

plaintext

        A(6, bf=2)/   \
D(4,0)      C(7, bf=1)  (原subRL,现在是A的右子树)/   \-     B(8, bf=0)  (原右孩子B,现在是C的右子树)\E(9,0)

此时,父节点 A 的右子树已变为 “纯右偏”(C 的右子树是 B,B 的右子树是 E),即 RR 失衡结构,可通过左单旋平衡。

步骤 2:对父节点(A)做左单旋(完成平衡)

目标:将拐弯节点 C(7) 提为父节点 A(6) 的父节点,消除 A 的高度差(bf=2)。

具体操作(对应代码 RotateL(parent)):
  1. 取父节点 A 的右子树 C,记录 C 的左子树(若有);
  2. 将 C 的左子树交给 A 当右子树(若 C->_left 非空,需更新其 _parent 为 A);
  3. 将 A 降为 C 的左子树,更新 A->_parent 为 C
  4. 若 A 原是根节点,更新 _root 为 C;否则,将 C 对接 A 的原父节点(pparent),维护树的整体链接。

步骤 2 后结构(最终平衡)

plaintext

        C(7, bf=0)/   \
A(6,0)      B(8, bf=0)\      \
D(4,0)      E(9,0)

此时所有节点的 bf 均为 0,左子树与右子树高度一致,AVL 树规则恢复。

四、RL 双旋代码解析

结合你提供的 AVL 树源码,RotateRL 函数是 RL 双旋的核心实现。需重点关注 节点指针操作 和 平衡因子(bf)更新——bf 的更新直接决定后续插入是否能正确判断失衡。

1. 完整代码

// RL双旋:先对右孩子做右单旋,再对父节点做左单旋
void RotateRL(Node* parent) 
{// 1. 定义关键节点:parent(失衡父节点)、subR(右孩子)、subRL(拐弯节点,subR的左子树)Node* subR = parent->_right;    // 父节点的右孩子(如示例中的B(8))Node* subRL = subR->_left;      // 拐弯节点(如示例中的C(7))int bf = subRL->_bf;            // 保存subRL的原始bf!这是后续更新bf的关键依据// 2. 第一步:对右孩子subR做右单旋,将RL转化为RR结构RotateR(subR);// 3. 第二步:对父节点parent做左单旋,完成平衡RotateL(parent);// 4. 根据subRL的原始bf,更新三个核心节点的bf(parent、subR、subRL)// 核心逻辑:subRL的bf决定了插入点的位置,进而影响旋转后子树的高度差if (bf == -1) {// 情况1:subRL的bf=-1 → 插入点在subRL的右子树(如C的右孩子9)// 旋转后:subR(B)的左子树高度 < 右子树,bf=1;其他节点bf归0subR->_bf = 1;subRL->_bf = 0;parent->_bf = 0;} else if (bf == 1) {// 情况2:subRL的bf=1 → 插入点在subRL的左子树(如C的左孩子5)// 旋转后:parent(A)的右子树高度 < 左子树,bf=-1;其他节点bf归0subR->_bf = 0;subRL->_bf = 0;parent->_bf = -1;} else if (bf == 0) {// 情况3:subRL的bf=0 → subRL本身是插入节点(无孩子)// 旋转后:三个节点的子树高度完全一致,bf均归0subR->_bf = 0;subRL->_bf = 0;parent->_bf = 0;} else {// 异常情况:bf只能是-1、0、1,触发断言排查错误assert(false);}
}

2.细节解读

(1)为什么要保存 subRL->_bf

subRL 的原始 bf 直接反映 “插入点的位置”:

  • bf=1:插入点在 subRL 的左子树 → 旋转后父节点 A 的左子树会偏高;
  • bf=-1:插入点在 subRL 的右子树 → 旋转后右孩子 B 的右子树会偏高;
  • bf=0subRL 是新插入节点 → 旋转后所有子树高度平衡。若不保存原始 bf,无法精准更新旋转后节点的 bf,会导致后续插入误判失衡。
(2)旋转函数的复用性

RotateRL 中直接调用了已实现的 RotateR(右单旋)和 RotateL(左单旋),无需重复编写指针操作逻辑 —— 这是代码模块化的体现,也避免了重复错误。

五、bf 更新逻辑详解(核心难点)

RL 双旋的 bf 更新与 LR 双旋对称,但容易因 “方向混淆” 出错。需结合 “插入点位置” 和 “旋转后子树高度变化” 理解,以下用具体场景验证:

场景 1:subRL->_bf = 1(插入点在 subRL 左子树)

示例:插入 5 到 C(7) 的左子树,subRL(C) 的 bf 变为 1(左子树高 1)。

  • 旋转后:parent(A) 的右子树(原 C 的左子树)被提走,左子树(D (4))比右子树高 1 → A->_bf = -1
  • subR(B) 的左子树为空,右子树(E (9))高 1 → B->_bf = 0
  • subRL(C) 的左右子树(A 和 B)高度一致 → C->_bf = 0

场景 2:subRL->_bf = -1(插入点在 subRL 右子树)

示例:插入 9 到 C(7) 的右子树,subRL(C) 的 bf 变为 -1(右子树高 1)。

  • 旋转后:subR(B) 的右子树(E (9))比左子树高 1 → B->_bf = 1
  • parent(A) 的左右子树(D 和 C 的左子树)高度一致 → A->_bf = 0
  • subRL(C) 的左右子树高度一致 → C->_bf = 0

场景 3:subRL->_bf = 0(subRL 是新插入节点)

示例:直接插入 7 作为 B(8) 的左子树,subRL(C) 的 bf 为 0(无孩子)。

  • 旋转后:ABC 的子树均无高度差 → 三者 bf 均为 0。

六、RL 双旋核心总结

  1. 适用场景:父节点 bf=2 + 右孩子 bf=-1(RL 失衡);
  2. 核心逻辑:先右旋右孩子(掰直为 RR 结构),再左旋父节点(平衡树);
  3. 关键注意
    • 必须保存 subRL 的原始 bf,用于精准更新旋转后节点的平衡因子;
    • 旋转时需维护所有节点的 _parent 指针,避免悬空(尤其是 subRL 的子树和原父节点的祖先);
  4. 与 LR 双旋的区别:方向完全对称,LR 是 “左子树先左后右”,RL 是 “右子树先右后左”,可对比记忆。

下面提供一下AVL树全部代码:

#include <iostream>
#include <string>
#include <assert.h>
using namespace std;

//AVL Tree;
namespace ym
{
template<class K, class V>
class AVLTreeNode
{
public:
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)
{
}
};

    //K->key(比较一般使用关键词key)
template<class K, class V>
class AVLTree
{
using Node = AVLTreeNode<K, V>;
public:

        AVLTree() = default; //强制生成默认构造

        bool Insert(const pair<K, V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
else
{
Node* parent = nullptr;
Node* pcur = _root;
while (pcur)
{
if (pcur->_kv.first < kv.first)
{
parent = pcur;
pcur = pcur->_right;
}
else if (pcur->_kv.first > kv.first)
{
parent = pcur;
pcur = pcur->_left;
}
else
{
return false;
}
}
pcur = new Node(kv);
if (parent->_kv.first < kv.first)
{
parent->_right = pcur;
}
else
{
parent->_left = pcur;
}
// 链接父亲
pcur->_parent = parent;
//更新平衡因子
while (parent)
{
if (pcur == parent->_left)
{
parent->_bf--;
}
else
{
parent->_bf++;
}
if (parent->_bf == 0)
{
break; //任然是AVL树
}
else if (parent->_bf == -1 || parent->_bf == 1)
{
pcur = parent;
parent = parent->_parent; //继续更新
}
else if (parent->_bf == -2 || parent->_bf == 2)
{
//旋转
if (parent->_bf == -2 && pcur->_bf == -1) //左边多且只插入到左子树的情况,即左边为h + 1, 右边为h,插入左边(h + 1)的左边
{
RotateR(parent);
}
else if (parent->_bf == 2 && pcur->_bf == 1)
{
RotateL(parent);
}
else if (parent->_bf == -2 && pcur->_bf == 1)
{
RotateLR(parent);
}
else if (parent->_bf == 2 && pcur->_bf == -1)
{
RotateRL(parent);
}
else
{
assert(false);
}
//退出
break;
}
else //如果在之前就已经不是AVL树,则返回assert报错
{
assert(false);
}
}
return true;
}
}

        // 单旋是纯粹的一边高
void RotateR(Node* parent) //左边多,右单旋
{
Node* subL = parent->_left; //左边
Node* subLR = subL->_right; //左边的右边
Node* pparent = parent->_parent; //父亲的父亲节点
//          6(p)
//     4(L)       7
//   3   5(LR)
//(插入)
parent->_left = subLR;
if (subLR) //非空就可以修改
subLR->_parent = parent;
parent->_parent = subL;
subL->_right = parent;
if (parent == _root)
{
_root = subL;
subL->_parent = nullptr;
}
else
{            
if (pparent->_left == parent)
{
pparent->_left = subL;
}
else
{
pparent->_right = subL;
}
subL->_parent = pparent;
}        
subL->_bf = 0;
parent->_bf = 0;
}

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

        //左右双旋(左拐右拐),先对subL进行左单旋,在对parent进行右单旋即可
//      4
//    3
//Null  (2)插入
// 左单旋
//      4
//    3
//  2  NULL
//右单旋
//      3
//    2   4
void RotateLR(Node* parent) //左右单旋
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(subL);
RotateR(parent);

            if (bf == 1)
{
subL->_bf = -1;
subLR->_bf = 0;
parent->_bf = 0;
}
else if (bf == -1)
{
subL->_bf = 0;
subLR->_bf = 0;
parent->_bf = 1;
}
else if (bf == 0)
{
subL->_bf = 0;
subLR->_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(subR);
RotateL(parent);

            if (bf == 0)
{
subR->_bf = 0;
subRL->_bf = 0;
parent->_bf = 0;
}
else if (bf == 1)
{
subR->_bf = 0;
subRL->_bf = 0;
parent->_bf = -1;
}
else if (bf == -1)
{
subR->_bf = 1;
subRL->_bf = 0;
parent->_bf = 0;
}
else
{
assert(false);
}
}

        bool Find(const K& key)
{
Node* pcur = _root;
while (pcur)
{
if (pcur->_kv.first < key)
{
pcur = pcur->_right;
}
else if (pcur->_kv.first > key)
{
pcur = pcur->_left;
}
else
{
return true;
}
}
return false;
}

        void InOrder()
{
_InOrder(_root);
}

int Height()
{
return _Height(_root);
}

        bool  IsBalanceTree()
{
return _IsBalanceTree(_root);
}

    private:
void _InOrder(const Node* root)
{
if (root == nullptr)
{
//cout << "Nullptr ";
return;
}
_InOrder(root->_left);
cout << root->_kv.first << ":" << root->_kv.second << endl;
_InOrder(root->_right);
}

        int _Height(Node* root)
{
if (root == nullptr)
return 0;

            int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);

            return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}

        bool _IsBalanceTree(Node* root)
{
//空树也是AVL树
if (nullptr == root)
return true;

            //计算pRoot结点的平衡因⼦:即pRoot左右⼦树的⾼度差
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
int diff = rightHeight - leftHeight;

            //如果计算出的平衡因⼦与pRoot的平衡因⼦不相等,或者
//pRoot平衡因⼦的绝对值超过1,则⼀定不是AVL树
if (abs(diff) >= 2)
{
cout << root->_kv.first << "⾼度差异常" << endl;
return false;
}

            if (root->_bf != diff)
{
cout << root->_kv.first << "平衡因⼦异常" << endl;
return false;
}

            // pRoot的左和右如果都是AVL树,则该树⼀定是AVL树
return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
}

        Node* _root = nullptr;
};
}

void TestAVLTree1()
{
ym::AVLTree<int, int> t;
// 常规的测试⽤例
int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
// 特殊的带有双旋场景的测试⽤例
//int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
for (auto& e : a)
{
t.Insert({ e, e });
}

    t.InOrder();
cout << t.IsBalanceTree() << endl;
}

int main()
{
TestAVLTree1();
return 0;
}

http://www.dtcms.com/a/474437.html

相关文章:

  • 南阳网站制作哪家好西安专业网站开发哪家好
  • 在 Windows PowerShell(pwsh)中配置 Oh My Posh + Conda 环境美化与性能优化
  • 小榄做网站新专业建设的重点任务
  • 把AI“浓缩”到1KB:超紧凑型决策树在MCU上的极限优化实战
  • Spring Boot 原理篇
  • 站酷网免费素材图库官网竣工验收全国公示平台
  • eclipse 导入javaweb项目,以及配置教程(傻瓜式教学)
  • 【Chrome插件】‘顾得助手’ 新功能介绍
  • 【控制系统建模与分析#1】电系统建模
  • 【Linux系统】9. 基础开发工具(三)
  • 付费网站做推广哪个好wordpress 顶部导航
  • 什么是AIGC?AIAIGCAGI什么区别?
  • NLP入门
  • 最低成本做企业网站 白之家杭州动漫设计公司最新招聘
  • 外汇跟单网站建设西安软件培训
  • 逻辑填空1【词的辨析】
  • 江油网站建设传媒公司业务范围介绍
  • 企业做网络推广有什么好处网站seo如何做
  • 成都网站开发建wordpress论坛用户
  • uzi粉丝做的网站wordpress 制作首页模板
  • 顺企网是什么网站flashfxp怎么上传网站
  • 【ChatGPT5】:“关于在当前 conda 环境里装 CUDA 12.8”
  • 网站建设水平如何评价建设商务网站
  • QT-常用控件(三)-显示类
  • 【多线程】阻塞等待(Blocking Wait)(以C++为例)
  • c语言动态内存管理
  • 传媒大气的网站网页设计与制作广东开放大学
  • AI 改变数据库产品实践探索
  • 做企业的网站都要准备什么怎么导出wordpress 整个网站
  • 做博客网站赚钱wordpress论坛社区主题