首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >C++进阶:(六)深入浅出分析AVL树:原理与实现

C++进阶:(六)深入浅出分析AVL树:原理与实现

作者头像
_OP_CHEN
发布2026-01-14 10:34:26
发布2026-01-14 10:34:26
1180
举报
文章被收录于专栏:C++C++

前言

在数据结构的世界里,二叉搜索树(BST)是一种高效的动态查找结构,其插入、删除和查找操作的时间复杂度均为 O (h)(h 为树的高度)。然而,普通二叉搜索树存在一个致命缺陷:当插入序列有序或接近有序时,树会退化为单链表,此时操作时间复杂度骤降至 O (n),完全失去了高效性。 为解决这一问题,自平衡二叉搜索树应运而生。AVL 树作为最早发明的自平衡二叉搜索树,由前苏联科学家 G. M. Adelson-Velsky 和 E. M. Landis 于 1962 年在论文《An algorithm for the organization of information》中提出。它通过严格控制左右子树的高度差,确保树始终保持平衡状态,从而将操作时间复杂度稳定在 O (log n) 级别。 本文将从 AVL 树的基本概念出发,详细讲解其结构设计、插入逻辑、平衡调整、查找实现及平衡检测方法,并提供完整的代码实现,帮助大家深入理解并掌握这一经典数据结构。下面就让我们正式开始吧!


一、AVL 树的核心概念

1.1 定义与性质

AVL 树是一种特殊的二叉搜索树,满足以下两个核心条件:

  1. 二叉搜索树性质:左子树中所有节点的键值小于根节点键值,右子树中所有节点的键值大于根节点键值,且左右子树也均为二叉搜索树
  2. 高度平衡性质:任意节点的左右子树高度差的绝对值不超过 1,且左右子树也均为 AVL 树。

1.2 平衡因子(Balance Factor)

为了方便判断节点是否平衡,引入平衡因子的概念:

  • 定义:节点的平衡因子 = 右子树高度 - 左子树高度
  • AVL 树要求:任意节点的平衡因子只能是 - 1、0 或 1

平衡因子就像 “风向标”,直观反映了节点左右子树的高度关系,是后续我们进行平衡调整操作的核心判断依据。

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

有人可能会有这样的疑问:既然要平衡,为什么不要求左右子树高度差为 0?这样不就完全平衡了吗?实际上,这是由树的节点数量决定的 —— 某些情况下,完全平衡是无法实现的。

例如:

  • 当树只有 2 个节点时:根节点有一个左孩子或右孩子,左右子树高度差为 1;
  • 当树有 4 个节点时:若根节点有左、右子树,左子树有一个孩子,右子树无孩子(或反之),高度差为 1。

因此,AVL 树选择 “高度差绝对值不超过 1” 作为平衡标准,既保证了树的高效性,又避免了无法实现的极端要求。

1.4 AVL 树的高度与性能

AVL 树的高度严格控制在 O (log n) 级别(n 为节点总数),这是因为其平衡性质限制了树的 “生长”:

  • 对于 n 个节点的 AVL 树,最小高度为⌊log₂n⌋(接近完全二叉树);
  • 最大高度约为 1.44log₂(n+2)-1.328,远小于普通二叉搜索树的最坏情况(O (n))。

因此,AVL 树的插入、删除、查找操作时间复杂度均为 O (log n),相比普通二叉搜索树有本质提升。

二、AVL 树的结构设计(C++)

AVL 树的节点需要存储键值对、左右孩子指针、父节点指针(用于后续平衡因子更新)以及平衡因子。下面通过 C++ 模板来实现一下 AVL 树的节点结构和树结构。

2.1 节点结构(AVLTreeNode)

代码语言:javascript
复制
template<class K, class V>
struct AVLTreeNode {
    // 存储键值对(适用于字典场景,如key-value映射)
    pair<K, V> _kv;
    // 左右孩子指针
    AVLTreeNode<K, V>* _left;
    AVLTreeNode<K, V>* _right;
    // 父节点指针(关键:用于插入后回溯更新平衡因子)
    AVLTreeNode<K, V>* _parent;
    // 平衡因子(初始为0,新节点无孩子)
    int _bf;

    // 构造函数:初始化节点
    AVLTreeNode(const pair<K, V>& kv)
        : _kv(kv)
        , _left(nullptr)
        , _right(nullptr)
        , _parent(nullptr)
        , _bf(0) {}
};

说明

  • 父节点指针_parent是 AVL 树的关键设计之一:插入新节点后,需要从新节点向上回溯,更新所有祖先节点的平衡因子,父节点指针确保了回溯的可行性;
  • 平衡因子_bf初始为 0:新节点无左、右孩子,左右子树高度差为 0。

2.2 树结构(AVLTree)

代码语言:javascript
复制
template<class K, class V>
class AVLTree {
    // 类型别名:简化节点指针的使用
    typedef AVLTreeNode<K, V> Node;
public:
    // 构造函数:默认初始化根节点为nullptr(空树)
    AVLTree() : _root(nullptr) {}

    // 后续实现:插入、查找、平衡检测等成员函数
    bool Insert(const pair<K, V>& kv);
    Node* Find(const K& key);
    bool IsBalanceTree();
    // 中序遍历(用于验证二叉搜索树性质)
    void InOrder() { _InOrder(_root); cout << endl; }

private:
    // 根节点(私有成员,通过公有接口访问)
    Node* _root;

    // 辅助函数:递归中序遍历
    void _InOrder(Node* root) {
        if (root == nullptr) return;
        _InOrder(root->_left);
        cout << root->_kv.first << " ";
        _InOrder(root->_right);
    }

    // 辅助函数:计算节点高度
    int _Height(Node* root);
    // 辅助函数:递归检测平衡
    bool _IsBalanceTree(Node* root);

    // 旋转操作(私有:仅内部平衡调整使用)
    void RotateR(Node* parent);  // 右单旋
    void RotateL(Node* parent);  // 左单旋
    void RotateLR(Node* parent); // 左右双旋
    void RotateRL(Node* parent); // 右左双旋
};

说明

  • 采用模板设计:支持任意可比较的键类型K和值类型V,通用性更强;
  • 私有辅助函数:_InOrder(中序遍历)、_Height(计算高度)、_IsBalanceTree(平衡检测)等,封装内部实现细节;
  • 旋转操作:RotateRRotateL等为私有成员,仅在插入后平衡调整时调用,外部无需感知。

三、AVL 树的插入实现

AVL 树的插入是其核心操作,分为 “按二叉搜索树规则插入” “回溯更新平衡因子并调整平衡” 两个阶段。

3.1 插入流程概述

  1. 按二叉搜索树规则插入:找到新节点的插入位置,创建节点并建立父子关系;
  2. 回溯更新平衡因子:从新节点的父节点开始,向上更新所有祖先节点的平衡因子,直到根节点或满足停止条件;
  3. 平衡检测与调整:若更新过程中某节点的平衡因子变为 ±2(失衡),则通过旋转操作调整子树平衡,调整后插入结束(旋转会降低子树高度,不影响上层节点)。

3.2 步骤 1:按二叉搜索树规则插入

首先,按照普通二叉搜索树的插入逻辑找到插入位置,代码如下:

代码语言:javascript
复制
bool AVLTree<K, V>::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 {
            // 键值已存在,插入失败(AVL树不允许重复键)
            return false;
        }
    }

    // 找到插入位置,创建新节点并建立父子关系
    cur = new Node(kv);
    if (parent->_kv.first < kv.first) {
        // 新节点为父节点的右孩子
        parent->_right = cur;
    } else {
        // 新节点为父节点的左孩子
        parent->_left = cur;
    }
    // 建立父节点指针(关键:用于后续回溯)
    cur->_parent = parent;

    // 后续步骤:更新平衡因子并调整平衡(见3.3节)
    // ...
}

细节分析

  • 不允许重复键:若插入的键已存在,直接返回false
  • 建立父节点指针:新节点的_parent指向其父节点,父节点的_left_right指向新节点,确保回溯路径完整。

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

插入新节点后,只有新节点的祖先节点的高度可能发生变化(子树高度变化会影响平衡因子)。因此,需要从父节点开始向上更新平衡因子,并判断是否需要停止或调整平衡。

3.3.1 平衡因子更新原则
  • 平衡因子定义:_bf = 右子树高度 - 左子树高度
  • 更新逻辑:
    • 若新节点是父节点的左孩子:父节点的左子树高度增加 1,因此parent->_bf--
    • 若新节点是父节点的右孩子:父节点的右子树高度增加 1,因此parent->_bf++
  • 停止条件:根据更新后父节点的平衡因子,决定是否继续向上更新(见下表)。
3.3.2 平衡因子更新停止条件

更新后父节点的平衡因子

含义(更新前后的变化)

是否继续向上更新

原因

0

之前为 ±1,插入后变为 0

插入前父节点子树 “一高一低”,插入后 “等高”,子树高度不变,不影响上层节点

±1

之前为 0,插入后变为 ±1

插入前父节点子树 “等高”,插入后 “一高一低”,子树高度增加 1,影响上层节点

±2

之前为 ±1,插入后变为 ±2

否(需旋转)

插入前父节点子树 “一高一低”,插入后 “更高的一侧更突出”,子树失衡,需旋转调整

3.3.3 代码实现:更新平衡因子
代码语言:javascript
复制
// 接3.2节代码,继续在Insert函数中实现
// 步骤2:回溯更新平衡因子
while (parent) {
    // 1. 更新当前父节点的平衡因子
    if (cur == parent->_left) {
        // 新节点是父节点的左孩子,父节点bf--
        parent->_bf--;
    } else {
        // 新节点是父节点的右孩子,父节点bf++
        parent->_bf++;
    }

    // 2. 判断是否需要继续更新或调整平衡
    if (parent->_bf == 0) {
        // 情况1:bf变为0,子树高度不变,停止更新
        break;
    } else if (parent->_bf == 1 || parent->_bf == -1) {
        // 情况2:bf变为±1,子树高度增加1,继续向上更新
        cur = parent;
        parent = parent->_parent;
    } else if (parent->_bf == 2 || parent->_bf == -2) {
        // 情况3:bf变为±2,子树失衡,需要旋转调整
        // 根据失衡类型选择对应的旋转操作
        if (parent->_bf == 2) {
            // 右子树过高,需左单旋或右左双旋
            if (cur->_bf == 1) {
                // 右子树的右子树过高(RR型),左单旋
                RotateL(parent);
            } else {
                // 右子树的左子树过高(RL型),右左双旋
                RotateRL(parent);
            }
        } else { // parent->_bf == -2
            // 左子树过高,需右单旋或左右双旋
            if (cur->_bf == -1) {
                // 左子树的左子树过高(LL型),右单旋
                RotateR(parent);
            } else {
                // 左子树的右子树过高(LR型),左右双旋
                RotateLR(parent);
            }
        }
        // 旋转后子树高度恢复,无需继续向上更新,插入结束
        break;
    } else {
        // 异常情况:bf绝对值超过2(代码逻辑错误)
        assert(false);
    }
}

return true;

代码说明如下

  • 循环条件:parent != nullptr(直到根节点);
  • 失衡判断:当parent->_bf == ±2时,根据cur->_bf(失衡子树的子节点平衡因子)判断失衡类型(LL、RR、LR、RL),选择对应的旋转操作;
  • 异常处理:若parent->_bf为其他值(如 3、-3),说明代码逻辑错误,通过assert断言提示。

四、AVL 树的旋转操作(平衡调整核心)

当某节点的平衡因子变为 ±2 时,子树失衡,需要通过旋转操作调整平衡。旋转的核心目标有两个:

  1. 恢复子树的平衡(任意节点的平衡因子回到 ±1 或 0);
  2. 降低子树的高度(恢复到插入前的高度,避免影响上层节点)。

根据失衡子树的结构,旋转分为四种类型:右单旋(LL 型)左单旋(RR 型)左右双旋(LR 型)右左双旋(RL 型)

4.1 右单旋(RotateR):处理 LL 型失衡

4.1.1 失衡场景

LL 型失衡:失衡节点(设为parent)的平衡因子为 - 2(左子树过高),且其左孩子(设为subL)的平衡因子为 - 1(左子树的左子树过高)。

例如:

代码语言:javascript
复制
        parent (bf=-2)
       /
    subL (bf=-1)
   /
 newNode
4.1.2 旋转原理

subL提升为新的子树根节点,parent变为subL的右孩子,subL的右子树(设为subLR)变为parent的左子树。具体步骤如下:

  1. 保存subL的右子树subLR
  2. subLR作为parent的左子树(若subLR非空,更新其_parentparent);
  3. parent作为subL的右子树,更新parent_parentsubL
  4. 处理subL与上层节点的关系(若parent是原根节点,则subL成为新根;否则,subL替换parent在其上层父节点中的位置);
  5. 重置parentsubL平衡因子为 0(旋转后子树平衡)。
4.1.3 代码实现
代码语言:javascript
复制
void AVLTree<K, V>::RotateR(Node* parent) {
    // 1. 保存关键节点
    Node* subL = parent->_left;    // parent的左孩子(即将成为新根)
    Node* subLR = subL->_right;    // subL的右子树(即将成为parent的左子树)
    Node* parentParent = parent->_parent; // parent的父节点(上层节点)

    // 2. 调整subLR与parent的关系
    parent->_left = subLR;
    if (subLR != nullptr) {
        subLR->_parent = parent;
    }

    // 3. 调整parent与subL的关系
    subL->_right = parent;
    parent->_parent = subL;

    // 4. 调整subL与上层节点的关系
    if (parentParent == nullptr) {
        // 情况1:parent是原根节点,subL成为新根
        _root = subL;
        subL->_parent = nullptr;
    } else {
        // 情况2:parent是上层节点的左/右孩子,subL替换其位置
        if (parentParent->_left == parent) {
            parentParent->_left = subL;
        } else {
            parentParent->_right = subL;
        }
        subL->_parent = parentParent;
    }

    // 5. 重置平衡因子(旋转后subL和parent均平衡)
    parent->_bf = 0;
    subL->_bf = 0;
}

示例图解如下:

4.2 左单旋(RotateL):处理 RR 型失衡

4.2.1 失衡场景

RR 型失衡失衡节点parent)的平衡因子为 2(右子树过高),且其右孩子subR)的平衡因子为 1(右子树的右子树过高)。

例如:

代码语言:javascript
复制
parent (bf=2)
       \
    subR (bf=1)
       \
     newNode
4.2.2 旋转原理

与右单旋对称:将subR提升为新的子树根节点,parent变为subR的左孩子,subR的左子树(subRL)变为parent的右子树。

4.2.3 代码实现
代码语言:javascript
复制
void AVLTree<K, V>::RotateL(Node* parent) {
    // 1. 保存关键节点
    Node* subR = parent->_right;   // parent的右孩子(即将成为新根)
    Node* subRL = subR->_left;     // subR的左子树(即将成为parent的右子树)
    Node* parentParent = parent->_parent; // parent的父节点

    // 2. 调整subRL与parent的关系
    parent->_right = subRL;
    if (subRL != nullptr) {
        subRL->_parent = parent;
    }

    // 3. 调整parent与subR的关系
    subR->_left = parent;
    parent->_parent = subR;

    // 4. 调整subR与上层节点的关系
    if (parentParent == nullptr) {
        // 情况1:parent是原根节点,subR成为新根
        _root = subR;
        subR->_parent = nullptr;
    } else {
        // 情况2:parent是上层节点的左/右孩子,subR替换其位置
        if (parentParent->_left == parent) {
            parentParent->_left = subR;
        } else {
            parentParent->_right = subR;
        }
        subR->_parent = parentParent;
    }

    // 5. 重置平衡因子
    parent->_bf = 0;
    subR->_bf = 0;
}

示例图解如下:

4.3 左右双旋(RotateLR):处理 LR 型失衡

4.3.1 失衡场景

LR 型失衡失衡节点parent)的平衡因子为 - 2(左子树过高),且其左孩子subL)的平衡因子为 1(左子树的右子树过高)。

例如:

代码语言:javascript
复制
        parent (bf=-2)
       /
    subL (bf=1)
       \
    subLR (newNode)
4.3.2 旋转原理

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

  1. 先以subL为旋转点进行左单旋,将subLR提升为subL的父节点(此时失衡类型转为 LL 型);
  2. 再以parent为旋转点进行右单旋,将subLR提升为新的子树根节点,恢复平衡。
4.3.3 平衡因子重置逻辑

旋转后平衡因子的重置需根据subLR(双旋的核心节点)插入前的平衡因子判断,分为三种场景:

  1. subLR->_bf == 0:插入前subLR无孩子,旋转后parentsubLsubLR平衡因子均为 0
  2. subLR->_bf == -1:插入前subLR的左子树有节点,旋转后parent->_bf = 1subL->_bf = 0subLR->_bf = 0
  3. subLR->_bf == 1:插入前subLR的右子树有节点,旋转后parent->_bf = 0subL->_bf = -1subLR->_bf = 0
4.3.4 代码实现
代码语言:javascript
复制
void AVLTree<K, V>::RotateLR(Node* parent) {
    // 1. 保存关键节点
    Node* subL = parent->_left;
    Node* subLR = subL->_right;
    // 记录subLR的平衡因子(用于后续重置)
    int bf = subLR->_bf;

    // 2. 第一步:以subL为旋转点进行左单旋(转为LL型)
    RotateL(subL);
    // 3. 第二步:以parent为旋转点进行右单旋(恢复平衡)
    RotateR(parent);

    // 4. 根据subLR的原始bf重置平衡因子
    if (bf == 0) {
        subL->_bf = 0;
        subLR->_bf = 0;
        parent->_bf = 0;
    } else if (bf == -1) {
        // subLR的左子树插入节点,parent的右子树高度增加
        subL->_bf = 0;
        subLR->_bf = 0;
        parent->_bf = 1;
    } else if (bf == 1) {
        // subLR的右子树插入节点,subL的左子树高度增加
        subL->_bf = -1;
        subLR->_bf = 0;
        parent->_bf = 0;
    } else {
        assert(false); // 异常情况
    }
}

示例图解如下:

4.4 右左双旋(RotateRL):处理 RL 型失衡

4.4.1 失衡场景

RL 型失衡失衡节点parent)的平衡因子为 2(右子树过高),且其右孩子subR)的平衡因子为 - 1(右子树的左子树过高)。

例如:

代码语言:javascript
复制
parent (bf=2)
       \
    subR (bf=-1)
    /
subRL (newNode)
4.4.2 旋转原理

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

  1. 先以subR为旋转点进行右单旋,将subRL提升为subR的父节点(此时失衡类型转为 RR 型);
  2. 再以parent为旋转点进行左单旋,将subRL提升为新的子树根节点,恢复平衡。
4.4.3 平衡因子重置逻辑

与左右双旋类似,根据subRL的原始平衡因子判断:

  1. subRL->_bf == 0:旋转后parentsubRsubRL平衡因子均为 0
  2. subRL->_bf == 1:旋转后parent->_bf = -1subR->_bf = 0subRL->_bf = 0
  3. subRL->_bf == -1:旋转后parent->_bf = 0subR->_bf = 1subRL->_bf = 0
4.4.4 代码实现
代码语言:javascript
复制
void AVLTree<K, V>::RotateRL(Node* parent) {
    // 1. 保存关键节点
    Node* subR = parent->_right;
    Node* subRL = subR->_left;
    // 记录subRL的平衡因子
    int bf = subRL->_bf;

    // 2. 第一步:以subR为旋转点进行右单旋(转为RR型)
    RotateR(subR);
    // 3. 第二步:以parent为旋转点进行左单旋(恢复平衡)
    RotateL(parent);

    // 4. 根据subRL的原始bf重置平衡因子
    if (bf == 0) {
        subR->_bf = 0;
        subRL->_bf = 0;
        parent->_bf = 0;
    } else if (bf == 1) {
        // subRL的右子树插入节点,parent的左子树高度增加
        subR->_bf = 0;
        subRL->_bf = 0;
        parent->_bf = -1;
    } else if (bf == -1) {
        // subRL的左子树插入节点,subR的右子树高度增加
        subR->_bf = 1;
        subRL->_bf = 0;
        parent->_bf = 0;
    } else {
        assert(false); // 异常情况
    }
}

示例图解如下:

五、AVL 树的查找实现

AVL 树的查找逻辑与普通二叉搜索树完全一致,因为平衡调整不会破坏二叉搜索树的性质。由于 AVL 树高度为 O (log n),查找时间复杂度也为 O (log n)

5.1 查找代码实现

代码语言:javascript
复制
template<class K, class V>
typename AVLTree<K, V>::Node* AVLTree<K, V>::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 {
            // 找到目标节点,返回指针
            return cur;
        }
    }
    // 遍历结束未找到,返回nullptr
    return nullptr;
}

代码说明

  • 使用typename关键字:因为Node是模板类AVLTree<K,V>的嵌套类型,编译器需要明确其为类型(而非成员变量);
  • 查找逻辑:通过键值比较,不断在左 / 右子树中缩小查找范围,直到找到目标或遍历结束。

5.2 查找示例

以 AVL 树{5,3,7,2,4,6,8}为例,查找键值为 6 的节点:

  1. 从根节点 5 开始,6>5,去右子树;
  2. 右子树节点 7,6<7,去左子树;
  3. 左子树节点 6,键值匹配,返回该节点。

六、AVL 树的平衡检测

为了验证实现的 AVL 树是否正确,需要通过代码检测其平衡性质。平衡检测包括两个核心:

  1. 任意节点的左右子树高度差绝对值不超过 1;
  2. 任意节点的平衡因子_bf等于 “右子树高度 - 左子树高度”(确保平衡因子更新正确)。

6.1 辅助函数:计算节点高度

首先实现_Height函数,递归计算节点的高度(空节点高度为 0,非空节点高度为左右子树最大高度 + 1):

代码语言:javascript
复制
template<class K, class V>
int AVLTree<K, V>::_Height(Node* root) {
    if (root == nullptr) {
        return 0;
    }
    // 递归计算左、右子树高度
    int leftHeight = _Height(root->_left);
    int rightHeight = _Height(root->_right);
    // 当前节点高度 = 左右子树最大高度 + 1
    return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}

6.2 平衡检测函数

实现_IsBalanceTree函数,递归检测每个节点的平衡性质:

代码语言:javascript
复制
template<class K, class V>
bool AVLTree<K, V>::_IsBalanceTree(Node* root) {
    // 空树是AVL树
    if (root == nullptr) {
        return true;
    }

    // 1. 计算当前节点的实际平衡因子(右高 - 左高)
    int leftHeight = _Height(root->_left);
    int rightHeight = _Height(root->_right);
    int actualBf = rightHeight - leftHeight;

    // 2. 检测平衡因子是否异常
    // 情况1:实际平衡因子绝对值超过1(失衡)
    if (abs(actualBf) >= 2) {
        cout << "节点" << root->_kv.first << "高度差异常:实际bf=" << actualBf << endl;
        return false;
    }
    // 情况2:存储的bf与实际bf不相等(平衡因子更新错误)
    if (root->_bf != actualBf) {
        cout << "节点" << root->_kv.first << "平衡因子异常:存储bf=" << root->_bf 
             << ",实际bf=" << actualBf << endl;
        return false;
    }

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

// 公有接口:对外提供平衡检测
template<class K, class V>
bool AVLTree<K, V>::IsBalanceTree() {
    return _IsBalanceTree(_root);
}

6.3 测试代码

通过插入测试用例,验证 AVL 树的平衡性质:

代码语言:javascript
复制
// 测试用例1:常规场景(包含双旋)
void TestAVLTree1() {
    AVLTree<int, int> t;
    // 插入序列:包含LR和RL双旋场景
    int a[] = {4, 2, 6, 1, 3, 5, 15, 7, 16, 14};
    for (auto e : a) {
        t.Insert({e, e});
    }

    // 验证二叉搜索树性质:中序遍历应为升序
    cout << "中序遍历结果:";
    t.InOrder(); // 预期:1 2 3 4 5 6 7 14 15 16

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

// 测试用例2:大量随机值插入(验证性能与稳定性)
void TestAVLTree2() {
    const int N = 100000; // 插入10万个随机值
    vector<int> v;
    v.reserve(N); // 预分配空间,避免扩容开销

    // 生成10万个不重复的随机值
    srand(time(0));
    for (size_t i = 0; i < N; i++) {
        v.push_back(rand() + i); // 加i避免重复
    }

    // 插入性能测试
    size_t insertBegin = clock();
    AVLTree<int, int> t;
    for (auto e : v) {
        t.Insert({e, e});
    }
    size_t insertEnd = clock();
    cout << "TestAVLTree2:插入" << N << "个节点耗时:" << (insertEnd - insertBegin) << "ms" << endl;

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

    // 树高度检测(应接近log2(N) ≈ 17)
    int height = t._Height(t._root);
    cout << "TestAVLTree2:AVL树高度为:" << height << endl;

    // 查找性能测试(查找随机值)
    size_t findBegin = clock();
    for (size_t i = 0; i < N; i++) {
        int key = rand() + i;
        t.Find(key);
    }
    size_t findEnd = clock();
    cout << "TestAVLTree2:查找" << N << "个随机值耗时:" << (findEnd - findBegin) << "ms" << endl;
}

// 主函数:运行测试用例
int main() {
    TestAVLTree1();
    cout << "------------------------" << endl;
    TestAVLTree2();
    return 0;
}

6.4 测试结果分析

  • TestAVLTree1:中序遍历结果为升序,平衡检测通过;
  • TestAVLTree2:插入 10 万个节点耗时短,树高度约为 17(符合 O (log n)),查找耗时短,说明 AVL 树性能优异。

七、AVL 树的删除(扩展内容)

本文在这里没有详细实现 AVL 树的删除操作,因为其逻辑是比插入更复杂的:

  • 删除节点后,同样需要回溯更新平衡因子;
  • 若出现失衡,需通过旋转调整,但旋转后可能导致上层节点继续失衡(插入时旋转后不会影响上层,删除时可能影响);
  • 需处理节点的三种删除情况(叶子节点、只有一个孩子、有两个孩子),并结合平衡调整。

若需深入学习,大家可以参考《殷人昆 数据结构:用面向对象方法与 C++ 语言描述》等经典教材。


总结

AVL 树作为经典的自平衡二叉搜索树,通过严格的平衡控制(左右子树高度差≤1)和高效的旋转操作,确保了插入、删除、查找的时间复杂度稳定在 O (log n) 级别,解决了普通二叉搜索树的退化问题。 AVL 树的实现难度主要在于旋转操作的逻辑和平衡因子的更新,建议读者结合代码手动模拟插入和旋转过程,加深理解。在实际工程中,AVL 树适用于对查找性能要求高、插入删除频率适中的场景(若插入删除频率极高,可考虑红黑树,其旋转次数更少)。 希望本文能帮助大家全面掌握 AVL 树的原理与实现,为后续学习更复杂的数据结构(如红黑树)打下坚实的基础!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2026-01-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 一、AVL 树的核心概念
    • 1.1 定义与性质
    • 1.2 平衡因子(Balance Factor)
    • 1.3 为什么不要求高度差为 0?
    • 1.4 AVL 树的高度与性能
  • 二、AVL 树的结构设计(C++)
    • 2.1 节点结构(AVLTreeNode)
    • 2.2 树结构(AVLTree)
  • 三、AVL 树的插入实现
    • 3.1 插入流程概述
    • 3.2 步骤 1:按二叉搜索树规则插入
    • 3.3 步骤 2:回溯更新平衡因子
      • 3.3.1 平衡因子更新原则
      • 3.3.2 平衡因子更新停止条件
      • 3.3.3 代码实现:更新平衡因子
  • 四、AVL 树的旋转操作(平衡调整核心)
    • 4.1 右单旋(RotateR):处理 LL 型失衡
      • 4.1.1 失衡场景
      • 4.1.2 旋转原理
      • 4.1.3 代码实现
    • 4.2 左单旋(RotateL):处理 RR 型失衡
      • 4.2.1 失衡场景
      • 4.2.2 旋转原理
      • 4.2.3 代码实现
    • 4.3 左右双旋(RotateLR):处理 LR 型失衡
      • 4.3.1 失衡场景
      • 4.3.2 旋转原理
      • 4.3.3 平衡因子重置逻辑
      • 4.3.4 代码实现
    • 4.4 右左双旋(RotateRL):处理 RL 型失衡
      • 4.4.1 失衡场景
      • 4.4.2 旋转原理
      • 4.4.3 平衡因子重置逻辑
      • 4.4.4 代码实现
  • 五、AVL 树的查找实现
    • 5.1 查找代码实现
    • 5.2 查找示例
  • 六、AVL 树的平衡检测
    • 6.1 辅助函数:计算节点高度
    • 6.2 平衡检测函数
    • 6.3 测试代码
    • 6.4 测试结果分析
  • 七、AVL 树的删除(扩展内容)
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档