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

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

目录

前言

一、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 树的删除(扩展内容)

总结


前言

        在数据结构的世界里,二叉搜索树(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)

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)

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:按二叉搜索树规则插入

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

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 代码实现:更新平衡因子

// 接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(左子树的左子树过高)。

        例如:

        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 代码实现

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(右子树的右子树过高)。

        例如:

parent (bf=2)\subR (bf=1)\newNode

4.2.2 旋转原理

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

4.2.3 代码实现

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(左子树的右子树过高)。

        例如:

        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 代码实现

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(右子树的左子树过高)。

        例如:

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 代码实现

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 查找代码实现

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;}}// 遍历结束未找到,返回nullptrreturn 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):

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);// 当前节点高度 = 左右子树最大高度 + 1return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}

6.2 平衡检测函数

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

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 树的平衡性质:

// 测试用例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 树的原理与实现,为后续学习更复杂的数据结构(如红黑树)打下坚实的基础!

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

相关文章:

  • N1刷机/救砖用U盘注意事项
  • 数据确权
  • 2025年RAG技术在不同行业的应用场景有哪些
  • 光接入网(OAN)介绍
  • 广州市越秀区建设和水务局网站wordpress栏目标题被调用
  • 01 初试模型的部署
  • StarRocks技术分享
  • 电子商务网站建设特色泰州做网站的
  • 多模态 AI 时代的数据困局与机遇,Bright Data 赋能LLM 训练以及AEO场景
  • 昇腾NPU性能调优实战:INT8+批处理优化Mistral-7B全记录
  • 未来之窗昭和仙君(五十五)标签票据打印模板设计器——东方仙盟筑基期
  • 10.26-11.5力扣数组刷题
  • 【C++闯关笔记】哈希表模拟实现unordered_map与unordered_set
  • 【Agentic RL 专题】四、深入浅出RAG原理与实战项目
  • 开源力量:GitCode+昇腾NPU 部署Mistral-7B-Instruct-v0.2模型的技术探索与经验总结
  • 网站被人做跳转了做网站横幅的软件
  • 暖色调网站什么网站上面能接点小活做
  • 网站栏目是什么上海城隍庙必吃美食
  • 智慧康养人形机器人——银发科技的革命者及在日本超老龄化社会的实验(中)
  • 微算法科技(NASDAQ MLGO)“自适应委托权益证明DPoS”模型:重塑区块链治理新格局
  • 小康AI家庭医生:以科技之翼,守陪伴之初心
  • 司马阅与铨亿科技达成生态战略合作,AI赋能工业领域智能化转型
  • 【旋智科技】SPC1158 MCU 参数要点和开发资料
  • 天元建设集团有限公司管理工资发放2个网站 同意内容 百度优化
  • 算法26.0
  • 二十二、STM32的ADC(二)(ADC单通道)
  • 芯谷科技--D3915高性能点阵/条形显示驱动器,点亮智能显示新时代
  • 空间革命:智慧档案馆三维立体一体化科技监控系统方案
  • 苏州网站建设代理装饰设计资质乙级
  • layui窗口标题