【C++】 手撕 AVLTree :从零实现自平衡二叉搜索树


AVL 树平衡二叉搜索树

一、AVL 树核心定义

AVL 树是自带平衡约束的二叉搜索树(BST),由 Adelson-Velsky 和 Landis 提出,是最早的自平衡二叉树。

  1. 满足二叉搜索树性质:左子树所有节点值 < 根节点 < 右子树所有节点值
  2. 满足平衡因子约束:任意节点左右子树高度差绝对值 ≤ 1
  3. 平衡因子公式:平衡因子 = 左子树高度 - 右子树高度
  4. 失衡判定:平衡因子为2-2时,树失去平衡,必须旋转修复

核心优势

普通 BST 极端情况会退化成链表,查询复杂度O(n);

AVL 树严格控高,查询、插入、删除稳定O(logn)。

二、基础概念:高度与平衡因子

  • 叶子节点高度:1
  • 空节点高度:0
  • 平衡因子取值仅允许:-1、0、1
  • 一旦超出范围,触发旋转调整

三、四大失衡场景与旋转修复

插入节点后只会出现四种失衡形态,对应四种旋转操作。

1. LL 左左失衡 → 右旋

  • 成因:左子树的左子树插入节点,左偏重
  • 操作:以失衡节点左孩子为轴心单次右旋
  • 效果:高度回归平衡,保留 BST 有序性

2. RR 右右失衡 → 左旋

  • 成因:右子树的右子树插入节点,右偏重
  • 操作:以失衡节点右孩子为轴心单次左旋
  • 最简修复旋转

3. LR 左右失衡 → 先左后右双旋

  • 成因:左孩子的右子树插入节点,左 - 右弯折失衡
  • 步骤:
    1. 对左子节点左旋,转为 LL 形态
    2. 对根节点右旋,完成平衡

4. RL 右左失衡 → 先右后左双旋

  • 成因:右孩子的左子树插入节点,右 - 左弯折失衡
  • 步骤:
    1. 对右子节点右旋,转为 RR 形态
    2. 对根节点左旋,恢复平衡

四、AVL 树节点结构

AVL树的节点相比普通二叉搜索树(BST),增加了两个关键属性:

  1. _parent:指向父节点的指针。这在插入后回溯更新平衡因子时非常关键。

  2. _bf(Balance Factor):平衡因子。我们定义为 右子树高度 - 左子树高度

cpp 复制代码
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;                    // 平衡因子 (RightHeight - LeftHeight)

    AVLTreeNode(const pair<K, V>& kv)
        :_kv(kv)
        , _left(nullptr)
        , _right(nullptr)
        , _parent(nullptr)
        , _bf(0)
    { }
};

五、 插入逻辑(Insert)

插入分为三个步骤:

  1. 按BST规则找到插入位置。

  2. 更新祖先节点的平衡因子。

  3. 若失衡,进行旋转处理。

cpp 复制代码
template<class K, class V>
class AVLTree
{
    typedef AVLTreeNode<K, V> Node;
public:
    bool insert(const pair<K, V>& kv)
    {
        // 1. 空树情况
        if (_root == nullptr)
        {
            _root = new Node(kv);
            return true;
        }

        // 2. 寻找插入位置
        Node* parent = nullptr;
        Node* cur = _root;
        while (cur)
        {
            if (cur->_kv.first < kv.first)
            {
                parent = cur;
                cur = cur->_right;
            }
            else if (cur->_kv.first > kv.first)
            {
                parent = cur;
                cur = cur->_left;
            }
            else
            {
                return false; // 不允许重复键值
            }
        }

        // 3. 插入新节点
        cur = new Node(kv);
        if (parent->_kv.first < kv.first)
        {
            parent->_right = cur;
        }
        else
        {
            parent->_left = cur;
        }
        cur->_parent = parent;

        // 4. 更新平衡因子并调整
        while (parent)
        {
            // 更新当前父节点BF
            if (cur == parent->_left)
                parent->_bf--; // 左边高了
            else
                parent->_bf++; // 右边高了

            // 情况A: BF变为0,说明高度没变,停止更新
            if (parent->_bf == 0)
            {
                break;
            }
            // 情况B: BF为 1 或 -1,需要继续向上回溯
            else if (parent->_bf == 1 || parent->_bf == -1)
            {
                cur = parent;
                parent = parent->_parent;
            }
            // 情况C: BF为 2 或 -2,出现失衡,需要旋转
            else if (parent->_bf == 2 || parent->_bf == -2)
            {
                if (parent->_bf == -2 && cur->_bf == -1)       // 左左 (LL)
                {
                    RotateR(parent);
                }
                else if (parent->_bf == 2 && cur->_bf == 1)   // 右右 (RR)
                {
                    RotateL(parent);
                }
                else if (parent->_bf == -2 && cur->_bf == 1)  // 左右 (LR)
                {
                    RotateLR(parent);
                }
                else if (parent->_bf == 2 && cur->_bf == -1)  // 右左 (RL)
                {
                    RotateRL(parent);
                }
                else
                {
                    assert(false); // 理论上不可能走到这里
                }
                break; // 旋转后树已平衡,退出循环
            }
            else
            {
                assert(false); // 平衡因子异常
            }
        }
        return true;
    }

六、 旋转操作详解

旋转是AVL树的核心。我们需要处理四种情况:

1. 右单旋 (RotateR) ------ 应对 LL 型

当左侧过高,且新增节点在左子树的左侧时发生。

cpp 复制代码
void RotateR(Node* parent)
    {
        Node* subL = parent->_left;
        Node* subLR = subL->_right;
        Node* parentParent = parent->_parent;

        // 重新连接节点
        parent->_left = subLR;
        if (subLR) subLR->_parent = parent;

        subL->_right = parent;
        parent->_parent = subL;

        // 处理原parent的父节点链接
        if (parentParent == nullptr) // parent是根节点
        {
            _root = subL;
            subL->_parent = nullptr;
        }
        else
        {
            if (parent == parentParent->_left)
                parentParent->_left = subL;
            else
                parentParent->_right = subL;
            subL->_parent = parentParent;
        }

        // 更新平衡因子 (右旋后,parent和subL的BF都归零)
        parent->_bf = subL->_bf = 0;
    }

2. 左单旋 (RotateL) ------ 应对 RR 型

当右侧过高,且新增节点在右子树的右侧时发生。

cpp 复制代码
void RotateL(Node* parent)
    {
        Node* subR = parent->_right;
        Node* subRL = subR->_left;
        Node* parentParent = parent->_parent;

        parent->_right = subRL;
        if (subRL) subRL->_parent = parent;

        subR->_left = parent;
        parent->_parent = subR;

        if (parentParent == nullptr)
        {
            _root = subR;
            subR->_parent = nullptr;
        }
        else
        {
            if (parent == parentParent->_left)
                parentParent->_left = subR;
            else
                parentParent->_right = subR;
            subR->_parent = parentParent;
        }

        parent->_bf = subR->_bf = 0;
    }

3. 左右双旋 (RotateLR) ------ 应对 LR 型

先对左子树左旋,再对自己右旋。

cpp 复制代码
void RotateLR(Node* parent)
    {
        Node* subL = parent->_left;
        Node* subLR = subL->_right;
        int bf = subLR->_bf; // 记录插入前的平衡因子

        RotateL(parent->_left); // 先左旋左孩子
        RotateR(parent);        // 再右旋自己

        // 根据 subLR 原来的 BF 调整最终平衡因子
        if (bf == 0)
        {
            subL->_bf = 0;
            subLR->_bf = 0;
            parent->_bf = 0;
        }
        else if (bf == -1) // 插入在 subLR 的左侧
        {
            subL->_bf = 0;
            subLR->_bf = 0;
            parent->_bf = 1;
        }
        else if (bf == 1) // 插入在 subLR 的右侧
        {
            subL->_bf = -1;
            subLR->_bf = 0;
            parent->_bf = 0;
        }
        else
        {
            assert(false);
        }
    }

4. 右左双旋 (RotateRL) ------ 应对 RL 型

先对右子树右旋,再对自己左旋。

cpp 复制代码
void RotateRL(Node* parent)
    {
        Node* subR = parent->_right;
        Node* subRL = subR->_left;
        int bf = subRL->_bf;

        RotateR(parent->_right);
        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);
        }
    }

七、 验证与辅助功能

为了确保我们的AVL树是正确的,我们需要编写验证函数。

cpp 复制代码
public:
    // 中序遍历 (应该是升序的)
    void InOrder()
    {
        _InOrder(_root);
        cout << endl;
    }

    // 判断是否平衡
    bool IsBalanceTree()
    {
        return _IsBalanceTree(_root);
    }

private:
    // 递归检查高度和BF是否匹配
    bool _IsBalanceTree(Node* root)
    {
        if (root == nullptr)
            return true;

        int leftHeight = _Height(root->_left);
        int rightHeight = _Height(root->_right);
        
        // 计算实际的平衡因子
        int bf = rightHeight - leftHeight;
        
        // 检查:1. 高度差是否合法 2. 存储的BF是否等于实际计算的BF
        if (abs(bf) >= 2 || bf != root->_bf)
        {
            cout << root->_kv.first << " 平衡因子异常" << endl;
            return false;
        }

        return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
    }

    int _Height(Node* root)
    {
        if (root == nullptr) return 0;
        return max(_Height(root->_left), _Height(root->_right)) + 1;
    }

    void _InOrder(Node* root)
    {
        if (root == nullptr) return;
        _InOrder(root->_left);
        cout << root->_kv.first << " ";
        _InOrder(root->_right);
    }

private:
    Node* _root = nullptr;
};

八、 总结

场景 处理方式
LL 型​ (Parent= -2 , Cur= -1) 右单旋 (RotateR)​
RR 型​ (Parent= 2 , Cur= 1) 左单旋 (RotateL)​
LR 型​ (Parent= -2 , Cur= 1) 左右双旋 (RotateLR)​
RL 型​ (Parent= 2 , Cur=- 1) 右左双旋 (RotateRL)​

实现AVL树的关键在于理清父子节点之间的指针关系,特别是在旋转过程中,不仅要改孩子指针,还要记得维护 _parent指针。


相关推荐
I Promise344 小时前
C++ 单例模式超详细讲解
开发语言·c++·单例模式
wuweijianlove4 小时前
算法性能优化中的数据流重构与依赖消解的技术6
算法
Agent手记4 小时前
智能财务对账Agent如何设计?2026金融大模型Agent架构设计与实战指引
人工智能·算法·ai·金融
之歆4 小时前
Day17_JavaScript高级核心垃圾回收执行上下文闭包完全指南(上)
开发语言·javascript·ecmascript
Emerson_20264 小时前
stack,queue,list的区别和联系
数据结构·c++·list·queue·stack
爱学习的章鱼哥4 小时前
AI编程学习笔记(I)
人工智能·笔记·学习·ai编程
·醉挽清风·4 小时前
学习笔记—MySQL—索引
笔记·学习·mysql
weixin_550083154 小时前
基于Python的豆瓣电影数据爬取与可视化分析
开发语言·python
计算机安禾4 小时前
【算法分析与设计】第5篇:最大子数组问题:分治与线性扫描的对比分析
算法
xyq20244 小时前
jQuery Mobile Data 属性详解
开发语言