C++ AVL 树:从原理到实现,掌握自平衡二叉搜索树

        AVL 树是最早发明的自平衡二叉搜索树(Self-Balancing Binary Search Tree),其核心目标是解决普通二叉搜索树(BST)在有序插入时退化为单支树(时间复杂度 O (N))的缺陷。通过自动调整树的结构,AVL 树保证任意节点的左右子树高度差不超过 1,从而将增删查操作的时间复杂度稳定在 O (logN)。本文将从 “概念→结构→核心操作” 的路径,系统讲解 AVL 树的原理与实现,帮你彻底掌握这一经典数据结构。

一、AVL 树的概念:什么是自平衡二叉搜索树?

1.1 AVL 树的定义

AVL 树本质是一棵满足以下条件的二叉搜索树:

  1. 左右子树均为 AVL 树(递归定义);
  2. 任意节点的左右子树高度差的绝对值 ≤ 1(平衡条件)。

为了直观判断平衡状态,AVL 树引入平衡因子(Balance Factor, BF) 的概念:平衡因子 = 右子树高度 - 左子树高度根据 AVL 树的平衡条件,任意节点的平衡因子只能是 -1、0、1

1.2 为什么不要求高度差为 0?

理想的 “完全平衡”(高度差为 0)虽能达到最优性能,但在很多场景下无法实现。例如:

        2 个节点的 AVL 树:根节点左子树为空,右子树高度 1,高度差 1(无法做到 0);

        4 个节点的 AVL 树:根节点左子树高度 1,右子树高度 2,高度差 1(无法做到 0)。

因此,AVL 树选择 “高度差≤1” 作为平衡条件,既保证了树的高度接近完全二叉树(高度为 log₂N),又避免了过度追求平衡导致的复杂操作。

1.3 AVL 树的核心价值

普通 BST 在最坏情况下(有序插入)会退化为单支树,增删查时间复杂度退化为 O (N);而 AVL 树通过自平衡机制,确保树的高度始终为 O (logN),从而将所有操作的时间复杂度稳定在 O (logN),适用于对效率要求较高的场景(如数据库索引、高频检索系统)。

二、AVL 树的结构设计

AVL 树的节点需要存储以下信息:

        键值对(Key-Value,支持映射场景);

        左 / 右子节点指针;

        父节点指针(用于插入后更新平衡因子和旋转操作);

        平衡因子(BF)。

2.1 节点结构定义

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) {}
};

2.2 AVL 树类框架

template <class K, class V>
class AVLTree {
    typedef AVLTreeNode<K, V> Node;
public:
    // 核心接口:插入、查找、平衡检测
    bool Insert(const pair<K, V>& kv);
    Node* Find(const K& key);
    bool IsBalanceTree(); // 验证AVL树是否平衡

private:
    // 辅助接口:旋转、高度计算、平衡检测递归实现
    void RotateR(Node* parent);   // 右单旋
    void RotateL(Node* parent);   // 左单旋
    void RotateLR(Node* parent);  // 左右双旋
    void RotateRL(Node* parent);  // 右左双旋
    int _Height(Node* root);      // 计算子树高度
    bool _IsBalanceTree(Node* root); // 递归检测平衡

private:
    Node* _root = nullptr; // 根节点
};

三、AVL 树的核心操作:插入与旋转

AVL 树的插入流程分为两步:

  1. 按二叉搜索树规则插入新节点;
  2. 回溯更新平衡因子,若出现不平衡(BF=±2),通过旋转调整树结构,恢复平衡。

3.1 插入流程详解

步骤 1:按 BST 规则插入新节点

与普通 BST 插入逻辑一致:从根节点开始比较,小于当前节点则向左,大于则向右,找到空位后创建新节点,并链接到父节点。

步骤 2:回溯更新平衡因子

插入新节点后,仅会影响 “新节点→根节点” 路径上的祖先节点的平衡因子(子树高度变化)。更新规则如下:

        若新节点在父节点的左子树:父节点的 BF -= 1;

        若新节点在父节点的右子树:父节点的 BF += 1;

        更新后根据父节点的 BF 值决定后续操作:

父节点 BF 值含义后续操作
0插入前父节点 BF 为 ±1,插入后子树高度不变停止更新(不影响上层节点)
±1插入前父节点 BF 为 0,插入后子树高度 + 1继续向上更新(影响上层节点)
±2插入前父节点 BF 为 ±1,插入后子树高度差超限触发旋转(恢复平衡后停止更新)
其他值逻辑错误(如 BF=3)断言报错
步骤 3:插入与平衡因子更新代码实现
bool Insert(const pair<K, V>& kv) {
    // 1. 空树:直接创建根节点
    if (_root == nullptr) {
        _root = new Node(kv);
        return true;
    }

    // 2. 按BST规则找到插入位置
    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 {
            // Key已存在,插入失败
            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) {
        // 4.1 更新当前父节点的BF
        if (cur == parent->_left) {
            parent->_bf--;
        } else {
            parent->_bf++;
        }

        // 4.2 根据BF值决定后续操作
        if (parent->_bf == 0) {
            // 子树高度不变,停止更新
            break;
        } else if (parent->_bf == 1 || parent->_bf == -1) {
            // 子树高度+1,继续向上更新
            cur = parent;
            parent = parent->_parent;
        } else if (parent->_bf == 2 || parent->_bf == -2) {
            // 不平衡,触发旋转
            if (parent->_bf == 2) {
                // 右子树过高:左单旋或右左双旋
                if (cur->_bf == 1) {
                    RotateL(parent);
                } else { // cur->_bf == -1
                    RotateRL(parent);
                }
            } else { // parent->_bf == -2
                // 左子树过高:右单旋或左右双旋
                if (cur->_bf == -1) {
                    RotateR(parent);
                } else { // cur->_bf == 1
                    RotateLR(parent);
                }
            }
            // 旋转后子树高度恢复,停止更新
            break;
        } else {
            // 异常BF值(如3),断言报错
            assert(false);
        }
    }

    return true;
}

3.2 旋转操作:恢复平衡的核心

当某节点的 BF 为 ±2 时,需通过旋转调整树结构,目标是:

  1. 恢复 AVL 树的平衡(BF∈{-1,0,1});
  2. 降低该子树的高度(恢复到插入前的高度,避免影响上层节点)。

根据不平衡的类型,旋转分为四种:右单旋、左单旋、左右双旋、右左双旋

3.2.1 右单旋(处理左子树过高)

适用场景:父节点 BF=-2,且左子节点 BF=-1(左子树的左子树过高)。

旋转原理(抽象模型):

假设有节点parent(BF=-2),左子节点subL(BF=-1),subL的右子树subLR(高度 h):

  1. subLR作为parent的左子树(若subLR非空,更新其 parent 指针);
  2. parent作为subL的右子树(更新parent的 parent 指针);
  3. subL作为新的根节点,链接到原parent的父节点;
  4. 重置parentsubL的 BF 为 0(旋转后子树高度恢复)。
右单旋代码实现:
void RotateR(Node* parent) {
    Node* subL = parent->_left;       // 左子节点
    Node* subLR = subL->_right;        // 左子节点的右子树
    Node* parentParent = parent->_parent; // 原父节点的父节点

    // 步骤1:subLR链接到parent的左子树
    parent->_left = subLR;
    if (subLR) {
        subLR->_parent = parent;
    }

    // 步骤2:parent链接到subL的右子树
    subL->_right = parent;
    parent->_parent = subL;

    // 步骤3:subL链接到原parent的父节点
    if (parentParent == nullptr) {
        // 原parent是根节点,更新_root
        _root = subL;
        subL->_parent = nullptr;
    } else {
        // 原parent是子树,链接到上层
        if (parent == parentParent->_left) {
            parentParent->_left = subL;
        } else {
            parentParent->_right = subL;
        }
        subL->_parent = parentParent;
    }

    // 步骤4:重置平衡因子
    parent->_bf = 0;
    subL->_bf = 0;
}
3.2.2 左单旋(处理右子树过高)

适用场景:父节点 BF=2,且右子节点 BF=1(右子树的右子树过高)。

旋转原理(抽象模型):

与右单旋对称:

  1. 将右子节点subR的左子树subRL作为parent的右子树;
  2. parent作为subR的左子树;
  3. subR作为新的根节点,链接到原parent的父节点;
  4. 重置parentsubR的 BF 为 0。
左单旋代码实现:
void RotateL(Node* parent) {
    Node* subR = parent->_right;       // 右子节点
    Node* subRL = subR->_left;        // 右子节点的左子树
    Node* parentParent = parent->_parent; // 原父节点的父节点

    // 步骤1:subRL链接到parent的右子树
    parent->_right = subRL;
    if (subRL) {
        subRL->_parent = parent;
    }

    // 步骤2:parent链接到subR的左子树
    subR->_left = parent;
    parent->_parent = subR;

    // 步骤3:subR链接到原parent的父节点
    if (parentParent == nullptr) {
        _root = subR;
        subR->_parent = nullptr;
    } else {
        if (parent == parentParent->_left) {
            parentParent->_left = subR;
        } else {
            parentParent->_right = subR;
        }
        subR->_parent = parentParent;
    }

    // 步骤4:重置平衡因子
    parent->_bf = 0;
    subR->_bf = 0;
}
3.2.3 左右双旋(处理左子树的右子树过高)

适用场景:父节点 BF=-2,且左子节点 BF=1(左子树的右子树过高)。

旋转原理:

左右双旋是 “左单旋 + 右单旋” 的组合:

  1. 对左子节点subL执行左单旋(将subL的右子树subLR提升为新的subL);
  2. 对原父节点parent执行右单旋(恢复平衡);
  3. 根据subLR的原始 BF 值,调整parentsubLsubLR的 BF(分三种场景):
subLR原始 BF旋转后 BF 值
0parent->_bf=0subL->_bf=0subLR->_bf=0
-1parent->_bf=1subL->_bf=0subLR->_bf=0
1parent->_bf=0subL->_bf=-1subLR->_bf=0
左右双旋代码实现:
void RotateLR(Node* parent) {
    Node* subL = parent->_left;
    Node* subLR = subL->_right;
    int bf = subLR->_bf; // 记录subLR原始BF

    // 步骤1:对subL执行左单旋
    RotateL(subL);
    // 步骤2:对parent执行右单旋
    RotateR(parent);

    // 步骤3:根据subLR原始BF调整平衡因子
    if (bf == 0) {
        subL->_bf = 0;
        subLR->_bf = 0;
        parent->_bf = 0;
    } else if (bf == -1) {
        subL->_bf = 0;
        subLR->_bf = 0;
        parent->_bf = 1;
    } else if (bf == 1) {
        subL->_bf = -1;
        subLR->_bf = 0;
        parent->_bf = 0;
    } else {
        assert(false);
    }
}
3.2.4 右左双旋(处理右子树的左子树过高)

适用场景:父节点 BF=2,且右子节点 BF=-1(右子树的左子树过高)。

旋转原理:

与左右双旋对称,是 “右单旋 + 左单旋” 的组合:

  1. 对右子节点subR执行右单旋(将subR的左子树subRL提升为新的subR);
  2. 对原父节点parent执行左单旋(恢复平衡);
  3. 根据subRL的原始 BF 值,调整parentsubRsubRL的 BF。
右左双旋代码实现:
void RotateRL(Node* parent) {
    Node* subR = parent->_right;
    Node* subRL = subR->_left;
    int bf = subRL->_bf; // 记录subRL原始BF

    // 步骤1:对subR执行右单旋
    RotateR(subR);
    // 步骤2:对parent执行左单旋
    RotateL(parent);

    // 步骤3:根据subRL原始BF调整平衡因子
    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);
    }
}

3.3 查找操作

AVL 树的查找逻辑与普通 BST 完全一致 —— 按 Key 的大小定向遍历,时间复杂度 O (logN)。

代码实现:

Node* Find(const K& key) {
    Node* cur = _root;
    while (cur) {
        if (cur->_kv.first < key) {
            cur = cur->_right;
        } else if (cur->_kv.first > key) {
            cur = cur->_left;
        } else {
            // 找到Key,返回节点指针(可通过->second访问Value)
            return cur;
        }
    }
    // Key不存在
    return nullptr;
}

四、AVL 树的平衡检测

为了验证 AVL 树的实现是否正确,需通过代码检测两点:

  1. 任意节点的左右子树高度差≤1;
  2. 任意节点的 BF 值与 “右高 - 左高” 的计算结果一致。

4.1 辅助函数:计算子树高度

int _Height(Node* root) {
    if (root == nullptr) {
        return 0;
    }
    // 递归计算左右子树高度,取最大值+1(当前节点高度)
    int leftH = _Height(root->_left);
    int rightH = _Height(root->_right);
    return leftH > rightH ? leftH + 1 : rightH + 1;
}

4.2 平衡检测函数

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

    // 计算当前节点的实际平衡因子(右高-左高)
    int leftH = _Height(root->_left);
    int rightH = _Height(root->_right);
    int realBF = rightH - leftH;

    // 检测:1. BF值是否匹配;2. 高度差是否≤1
    if (root->_bf != realBF || abs(realBF) > 1) {
        cout << "节点Key:" << root->_kv.first 
             << ",记录BF:" << root->_bf 
             << ",实际BF:" << realBF << "(不平衡)" << endl;
        return false;
    }

    // 递归检测左右子树
    return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
}

// 对外接口
bool IsBalanceTree() {
    return _IsBalanceTree(_root);
}

4.3 测试代码

void TestAVLTree() {
    // 测试用例:包含双旋场景
    int a[] = {4, 2, 6, 1, 3, 5, 15, 7, 16, 14};
    AVLTree<int, int> t;
    for (auto e : a) {
        t.Insert({e, e});
    }

    // 中序遍历(验证BST特性:有序)
    cout << "中序遍历(有序):";
    t.InOrder(); // 需自行实现InOrder接口
    cout << endl;

    // 平衡检测
    if (t.IsBalanceTree()) {
        cout << "AVL树平衡检测通过!" << endl;
    } else {
        cout << "AVL树平衡检测失败!" << endl;
    }

    // 查找测试
    Node* ret = t.Find(7);
    if (ret) {
        cout << "找到Key=7,Value=" << ret->_kv.second << endl;
    } else {
        cout << "未找到Key=7" << endl;
    }
}

五、AVL 树的删除(扩展)

AVL 树的删除逻辑比插入更复杂,核心难点在于:

  1. 删除节点后,回溯更新平衡因子时,可能触发多轮旋转(插入仅需一轮);
  2. 若删除节点的子树高度降低,需继续向上更新,直到根节点或 BF 恢复为 ±1。

由于删除操作在实际应用中频率较低(且实现复杂),本文暂不展开,感兴趣的读者可参考《数据结构(殷人昆版)》或 STL 源码中 AVL 树的实现。

六、总结

AVL 树是平衡二叉搜索树的经典实现,其核心价值在于通过 “平衡因子 + 旋转” 机制,将树的高度稳定在 O (logN),确保增删查操作的时间复杂度为 O (logN)。关键要点总结如下:

  1. 平衡条件:任意节点的左右子树高度差≤1,BF∈{-1,0,1};
  2. 插入流程:BST 插入→回溯更新 BF→旋转恢复平衡(四种旋转场景);
  3. 旋转目标:恢复平衡的同时降低子树高度,避免影响上层节点;
  4. 适用场景:高频检索、低频率删除的场景(如内存数据库索引)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值