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

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

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

LR 双旋是处理 LR 失衡 的组合操作 —— 当某节点的左子树高度比右子树高 2parent.bf=-2),但左孩子的右子树更高subL.bf=1)时,直接右单旋无效,需要先对左孩子做左单旋,把结构转化为 LL 失衡,再对原 parent 做右单旋。

简单说:左子树 “先左后右拐”(LR),先把 “拐弯处” 的节点(subLR)提上来,把结构掰成 “纯左偏”(LL),再用右单旋平衡。

二、LR 失衡场景特征

A[parent:6, bf=-2] --> B[subL:4, bf=1]  // subL的右子树更高A --> C[右子树:7, bf=0]B --> D[subL的左子树:3, bf=0]B --> E[subLR:5, bf=0]  // 拐弯处节点,导致LR失衡E --> F[插入点:比如5的左/右]  // 插入引发失衡
  • 核心特征:parent.bf=-2subL.bf=1,失衡由subL的右子树(subLR)过高引发;
  • 问题:直接对 parent 做右单旋会导致 subLR 的子树 “挂错地方”,无法平衡。

三、LR 双旋步骤拆解

LR 双旋分 “两步走”:先旋子树,再旋父节点。

步骤 1:对 subL 做左单旋(掰直结构)

目标:把 subLR(5)提为 subL(4)的父节点,将 LR 结构转化为 LL 结构。

  • 执行RotateL(subL)(对节点 4 做左单旋):
    1. subLR(5)的左孩子(若有)交给 subL(4)当右孩子;
    2. subL(4)降为 subLR(5)的左孩子;
    3. subLR(5)成为原 parent(6)的左孩子。

步骤 2:对 parent 做右单旋(平衡树)

目标:把 subLR(5)提为 parent(6)的父节点,完成平衡。

  • 执行RotateR(parent)(对节点 6 做右单旋):
    1. subLR(5)的右孩子交给 parent(6)当左孩子;
    2. parent(6)降为 subLR(5)的右孩子;
    3. subLR(5)对接 pparent(若有),成为新根(若 parent 原是根)。

最终平衡结构:

subLR:5, bf=0

subL:4, bf=0

parent:6, bf=0

左子树:3, bf=0

右子树:7, bf=0

四、LR 双旋代码解析

void RotateLR(Node* parent) // 左右单旋(先左旋subL,再右旋parent)
{Node* subL = parent->_left;    // 原左孩子Node* subLR = subL->_right;    // 拐弯处节点(关键)int bf = subLR->_bf;           // 保存subLR原始bf,决定旋转后bf值// 步骤1:对subL做左单旋,转化为LL失衡RotateL(subL);// 步骤2:对parent做右单旋,完成平衡RotateR(parent);// 关键:根据subLR原始bf更新三个节点的bfif (bf == 1){// subLR的左子树过高:subL(4)的右子树高,bf=-1;其他归0subL->_bf = -1;subLR->_bf = 0;parent->_bf = 0;}else if (bf == -1){// subLR的右子树过高:parent(6)的左子树高,bf=1;其他归0subL->_bf = 0;subLR->_bf = 0;parent->_bf = 1;}else if (bf == 0){// subLR是叶子节点:三者bf均归0subL->_bf = 0;subLR->_bf = 0;parent->_bf = 0;}else{assert(false); // 非法bf,排查错误}
}

五、bf 更新逻辑详解(为什么分三种情况?)

subLR 的原始 bf 决定了旋转后子树的高度差:

  1. bf=1:subLR 的左子树过高(插入点在 subLR 左侧)→ 旋转后 subL(4)的右子树比左子树高 1,故subL.bf=-1
  2. bf=-1:subLR 的右子树过高(插入点在 subLR 右侧)→ 旋转后 parent(6)的左子树比右子树高 1,故parent.bf=1
  3. bf=0:subLR 是叶子节点(插入点就是 subLR)→ 旋转后三者子树高度相等,bf 均为 0。

六、LR 双旋核心总结

  1. 适用场景:LR 失衡(parent.bf=-2subL.bf=1);
  2. 核心逻辑:先左旋 subL “掰直” 成 LL,再右旋 parent 平衡;
  3. 关键注意:必须保存 subLR 的原始 bf,用于旋转后精准更新平衡因子

总之,对于左右双旋还是得自己画图理解,需要从具体到抽象,最后得出结论,送上一句话,绝知此事要躬行

附上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/470558.html

相关文章:

  • 查询类网站开发建设网站公司怎么收费
  • 电影发布网站模板WordPress云虚拟空间
  • Android获取外部存储目录
  • AUTOSAR模块架构
  • 简单易做的网站一级域名二级域名区别
  • 一个可计算宇宙模型:热力学规则驱动量子化弹性两层底空间演化的可编程物理模拟自动机设计-从量子过程到数值相对论模拟
  • 编程语言的选择策略:从C语言的OOP到AI与GUI开发的全方位对比
  • 网站 案例展示泗洪做网站公司
  • 做竞拍网站合法吗有谁认识做微网站的
  • 运行smolvlm解析视频
  • 【力扣】hot100系列(三)链表(一)(图示+多解法+时间复杂度分析)
  • 【Linux】linux基础指令入门(1)
  • 广东网站制作公司校园网站建设结论
  • 广州商城型网站福州核酸检测最新通知
  • 关于 旁注
  • mysql数据库介绍
  • Java程序设计
  • JavaScript从入门到实战 (1):JS 入门第一步:它是什么?能做什么?环境怎么搭?
  • Shell 中 $@ 与 $* 的核心区别:双引号包裹下的关键差异解析
  • 重庆网站seo网站外链购买平台
  • 乐清做网站哪家好汕头第一网e京网
  • 为什么ES中不推荐使用wildcard查询
  • 怎么叫人做网站高端的网站推广
  • ICT 数字测试原理 18 - -VCL如何对设备进行预处理
  • 19-基于STM32的人体体征监测系统设计与实现
  • 第12讲:深入理解指针(2)——指针的“安全锁”与“传址魔法”
  • 小企业网站制作wordpress 搭建个人博客
  • 企石镇做网站中国建筑装饰网饶明富
  • 深入洞察:从巴菲特投资哲学萃取最佳实践
  • 设计网站的功能有哪些微营销工具