【数据结构】平衡二叉树(AVL树)
目录
案例1:删除叶子节点(delKey=24,假设为右子树叶子节点)
(二)findMinNode/findMaxNode:最值节点查找
(三)树销毁(destroyAVLTree):后序遍历与内存释放
1. 右旋转函数:`rightRotate(AVLNode* A)`(处理 LL 型失衡)
2. 左旋转函数:`leftRotate(AVLNode* A)`(处理 RR 型失衡)
3. 左右双旋转函数:`leftRightRotate(AVLNode* A)`(处理 LR 型失衡)
4. 右左双旋转函数:`rightLeftRotate(AVLNode* A)`(处理 RL 型失衡)
(三)插入操作:`insertAVL(AVLNode*& root, int key, bool& taller)`
(四)删除操作:`deleteAVL(AVLNode*& root, int key, bool& shorter)`
一、引言:平衡二叉树的重要性与技术背景
在计算机科学领域,高效的数据组织与检索机制是支撑各类复杂应用的基础。二叉搜索树(Binary Search Tree, BST)作为一种经典的数据结构,通过左子树节点值小于根节点、右子树节点值大于根节点的特性,实现了数据的快速查找、插入和删除操作,理想情况下时间复杂度可达 O(log n)。然而,这一高效性依赖于树结构的平衡性——当面临频繁的插入与删除操作时,普通二叉搜索树可能逐渐退化为链表结构,导致操作效率从 O(log n) 急剧下降至 O(n),严重影响系统性能。
普通二叉搜索树的性能瓶颈:在极端情况下(如持续插入有序数据),二叉搜索树会退化为单链表结构。此时,查找、插入和删除操作的时间复杂度将从理想的 O(log n) 退化为 O(n),丧失其高效检索的优势。
为解决这一结构性缺陷,自平衡二叉搜索树(Self-Balancing Binary Search Tree)应运而生,其中 AVL 树(以发明者 Adelson-Velsky 和 Landis 命名)是最早提出的自平衡方案之一。AVL 树通过两项核心机制维持结构平衡:一是为每个节点维护平衡因子(Balance Factor,定义为左子树高度与右子树高度之差,取值范围为 {-1, 0, 1}),二是在平衡因子超出允许范围时执行旋转操作(包括左旋、右旋及复合旋转)。这两项机制共同确保了 AVL 树的高度始终保持在 O(log n) 级别,从而稳定维持各项操作的 O(log n) 时间复杂度。
AVL 树的这种特性使其在对性能敏感的场景中具有不可替代的价值。例如,在数据库索引系统中,AVL 树能够高效支持范围查询与动态数据更新;在有序数据动态查找表(如实时排行榜、股票价格序列)中,其平衡结构确保了即使在高频数据变更下仍能保持快速响应。理解 AVL 树的理论基础与实现细节,不仅是掌握高级数据结构的关键,也是构建高性能系统的重要基础——这正是本文后续章节将深入探讨其定义、节点结构、旋转操作及完整代码实现的核心原因。
二、平衡二叉树的理论基础
平衡二叉树作为二叉搜索树的优化结构,其核心理论基础在于通过动态维持树的平衡状态,确保数据操作的高效性。从定义上看,平衡二叉树是指任意节点的左右子树高度差(平衡因子)的绝对值不超过 1的二叉搜索树,这种约束使得树的结构始终保持相对“扁平”,避免了极端情况下退化为链表的风险。AVL 树作为最早被发明的平衡二叉树之一,由 Adelson-Velsky 和 Landis 于 1962 年提出,其命名即源于两位发明者的姓名首字母。
(一)平衡因子:量化平衡状态的核心指标
AVL 树通过平衡因子(Balance Factor, BF) 量化节点的平衡状态,其定义为左子树深度减去右子树深度(在代码实现中对应 AVLNode 结构体的 bf 成员)。根据平衡约束,AVL 树正常状态下所有节点的平衡因子取值范围严格限定为 {-1, 0, 1}。这一取值范围直接反映了节点左右子树的高度关系:
• BF = 1:左子树比右子树深 1 层;
• BF = 0:左右子树深度相等;
• BF = -1:右子树比左子树深 1 层。
平衡状态判定准则:当节点的平衡因子绝对值大于 1(即 BF ∈ {-2, 2, ...})时,树结构发生失衡。失衡通常发生在插入或删除节点后,此时需通过旋转操作(如单旋、双旋)调整子树结构,使平衡因子恢复至合法范围。
(二)平衡维护的理论必要性
平衡二叉树的理论价值体现在对时间复杂度的严格控制。对于普通二叉搜索树,在最坏情况下(如节点按升序或降序插入)会退化为线性结构,导致查找、插入、删除等操作的时间复杂度劣化为 O(n)。而 AVL 树通过动态平衡机制,确保树的高度始终保持在 O(log n) 级别(其中 n 为节点总数),从而使上述操作的时间复杂度稳定维持在 O(log n)。这种稳定性源于平衡因子的实时监控与失衡后的即时调整,本质上是通过牺牲少量插入/删除时的调整成本,换取查询操作的高效性,尤其适用于查询密集型场景。
从数据结构设计角度看,AVL 树的平衡理论为后续平衡二叉树(如红黑树、B 树)提供了核心思想:通过定义量化的平衡指标(如平衡因子、颜色标记)和对应的调整规则,实现树结构的动态平衡。这种设计思路成为高效动态数据管理的基础理论之一。
三、AVL树节点结构设计与实现
AVL树节点作为树结构的基本构成单元,其设计需同时满足二叉搜索树(BST)的有序性要求与自平衡机制的实现需求。节点结构通过struct AVLNode实现,具体定义如下:
struct AVLNode {
int key; // 关键字,用于维持二叉搜索树性质
int bf; // 平衡因子:左子树深度 - 右子树深度,用于失衡检测
AVLNode* left; // 左子指针
AVLNode* right; // 右子指针
AVLNode(int k) : key(k), bf(0), left(nullptr), right(nullptr) {}
};
核心成员设计解析
key成员:存储节点的关键字值,是维持二叉搜索树性质的核心要素。根据BST定义,左子树所有节点的key值需小于当前节点,右子树所有节点的key值需大于当前节点,这一特性确保了树的中序遍历结果为有序序列,为后续的查找、插入、删除等操作提供了逻辑基础。
bf成员:平衡因子(Balance Factor)定义为“左子树深度 - 右子树深度”,是量化节点平衡状态的关键指标。其初始值设为0,因为新创建的节点为叶子节点(左右子树深度均为0),此时节点处于绝对平衡状态。在AVL树的插入、删除等操作中,bf值会动态更新,当其绝对值大于1时,表明节点已失衡,需触发旋转调整以恢复平衡。
left与right指针:作为节点间的物理连接,分别指向左子节点与右子节点,初始值为nullptr。这两个指针通过层级嵌套构建了树的拓扑结构,使得AVL树能够通过递归或迭代方式进行深度优先(前序、中序、后序)或广度优先遍历。
构造函数:通过参数k初始化key值,并将bf设为0、left与right设为nullptr。这一初始化逻辑确保新节点在创建时即满足“叶子节点”的平衡条件,为后续树操作中平衡状态的维护奠定基础。
节点设计的核心考量:AVL树节点结构通过key维持数据有序性,bf实现失衡预警,指针构建物理结构,三者协同确保了树的动态平衡。构造函数的初始化策略则从源头避免了新节点引入的不平衡风险,为插入、删除等操作的高效执行提供了结构保障。
节点结构的设计直接影响AVL树后续操作的复杂度。例如,bf的实时维护避免了通过全树遍历计算深度来检测失衡的低效性,使得平衡调整可在O(1)时间内判断触发条件;而key的有序性则确保了查找操作可保持BST的O(log n)时间复杂度。这种“数据+状态+连接”的三位一体设计,是AVL树兼具有序性与平衡性的基础。
四、核心旋转操作:原理与实现
(一)LL型失衡与右旋转操作
LL 型失衡是 AVL 树中常见的失衡场景,具体指在某节点的左子树的左子树中插入新节点后,导致该节点的平衡因子变为 2(即左子树深度比右子树深 2 层)。此时需通过右旋转操作恢复树的平衡,其核心原理是调整失衡节点及其左子节点的位置关系,使树的左右子树深度重新达到平衡。
右旋转操作步骤与原理
右旋转操作针对 LL 型失衡设计,通过以下步骤实现平衡恢复(设失衡节点为 A,其左子节点为 B):
1. 指针调整阶段:
◦ 将 B 的右子树作为 A 的左子树(A->left = B->right),此时 B 的右子树从 B 转移至 A,避免子树丢失;
◦ 将 A 作为 B 的右子树(B->right = A),完成节点位置的核心交换。
2. 平衡因子重置阶段:
旋转后,A 和 B 的左右子树深度相等。由于原失衡源于 B 的左子树深度增加,旋转后 B 的右子树为 A,A 的左子树为原 B 的右子树,两者深度差消除,因此 A 和 B 的平衡因子均重置为 0。
3. 新根节点确定:
B 取代 A 成为新的子树根节点,返回 B 完成旋转操作。
右旋转操作核心逻辑:通过交换失衡节点 A 与左子节点 B 的位置,将 B 的右子树转移至 A 的左子树,使原本偏向左侧的深度分布重新平衡。旋转后 A 和 B 的平衡因子均为 0,确保子树满足 AVL 树的平衡条件。
代码实现与解析
右旋转操作的代码实现需严格遵循上述指针调整与平衡因子重置逻辑,具体 C++ 代码如下:
AVLNode* rightRotate(AVLNode* A) {
AVLNode* B = A->left; // 定位失衡节点 A 的左子节点 B
A->left = B->right; // 将 B 的右子树转移为 A 的左子树
B->right = A; // 将 A 作为 B 的右子树
A->bf = 0; // 重置 A 的平衡因子
B->bf = 0; // 重置 B 的平衡因子
return B; // B 成为新的子树根节点
}
代码解析:
• 变量 B 指向 A 的左子节点,是旋转后的新根;
• A->left = B->right 确保 B 的右子树(可能为空)被 A 接管,避免数据丢失;
• B->right = A 完成 A 与 B 的父子关系交换;
• 平衡因子重置为 0 的依据是旋转后 A 和 B 的左右子树深度差为 0,符合 AVL 树平衡因子定义(左子树深度减右子树深度)。
实例说明
以插入序列 10, 5, 3 为例,分析 LL 型失衡及右旋转过程:
1. 初始插入 10,树仅含根节点 10(平衡因子 0);
2. 插入 5,10 的左子树深度为 1,平衡因子变为 -1(符合平衡条件);
3. 插入 3,5 的左子树深度为 1,导致 10 的左子树深度为 2,右子树深度为 0,平衡因子变为 2,触发 LL 型失衡。
此时对失衡节点 10 执行右旋转:
• B 为 5(A 的左子节点),B 的右子树为空;
• A(10)的左子树设为 B 的右子树(仍为空);
• B 的右子树设为 A(10);
• 重置 A(10)和 B(5)的平衡因子为 0;
• 返回 B(5)作为新根,树结构变为 5 为根,左子树 3,右子树 10,此时所有节点平衡因子均为 0,恢复平衡。
通过右旋转操作,LL 型失衡节点及其子树的深度分布得到修正,确保 AVL 树在动态插入过程中始终维持平衡特性。
(二)RR型失衡与左旋转操作
RR型失衡是AVL树中另一种典型的失衡场景,与LL型失衡具有对称性。当在节点的右子树的右子树中插入新节点后,会导致该节点的平衡因子变为-2(即右子树深度比左子树深2层),此时需通过左旋转操作恢复树的平衡状态。
RR型失衡场景分析
RR型失衡的触发条件可描述为:设某节点为A,其右子节点为B,若在B的右子树(即A的右子树的右子树)中插入新节点后,A的平衡因子变为-2,则判定为RR型失衡。这种失衡模式与LL型失衡(左子树的左子树插入导致平衡因子+2)呈镜像对称,因此其调整策略也遵循对称逻辑。
左旋转操作原理与步骤
左旋转操作通过调整失衡节点及其右子节点的指针关系,重新分配子树结构以恢复平衡。具体步骤如下:
RR型左旋转核心操作步骤:
1. 定位节点:设失衡节点为A,其右子节点为B(B为A右子树的根节点);
2. 指针重定向1:将B的左子树作为A的新右子树(即A->right = B->left),确保B的左子树不会丢失;
3. 指针重定向2:将A作为B的左子树(即B->left = A),形成新的父子关系;
4. 平衡因子重置:由于旋转后A和B所在的子树深度恢复平衡,两者的平衡因子均重置为0;
5. 更新根节点:返回B作为旋转后子树的新根节点。
平衡因子重置原理
旋转操作后,A和B的平衡因子均被重置为0,这是因为:在RR型失衡场景中,插入操作发生在B的右子树,导致A的右子树深度比左子树深2层。通过左旋转,B成为新的根节点,A变为B的左子节点,此时A的左右子树深度相等(原左子树与B的原左子树深度一致),B的左右子树深度也相等(原右子树与包含A的新左子树深度一致),因此两者平衡因子均恢复为0。
代码实现与解析
左旋转操作的代码实现需严格对应上述步骤,确保指针调整和平衡因子更新的准确性。C++实现代码如下:
AVLNode* leftRotate(AVLNode* A) {
AVLNode* B = A->right; // 步骤1:获取失衡节点A的右子节点B
A->right = B->left; // 步骤2:B的左子树成为A的右子树
B->left = A; // 步骤3:A成为B的左子树
A->bf = 0; // 步骤4:重置A的平衡因子
B->bf = 0; // 步骤4:重置B的平衡因子
return B; // 步骤5:返回B作为新根节点
}
代码中,通过A->right = B->left和B->left = A完成核心指针调整,确保子树结构的完整性;平衡因子重置为0的操作直接反映了旋转后子树的深度平衡状态。
与LL型旋转的对称性对比
RR型左旋转与LL型右旋转在操作逻辑上呈镜像对称:
• 失衡场景对称:RR型为右子树的右子树插入(平衡因子-2),LL型为左子树的左子树插入(平衡因子+2);
• 旋转方向对称:RR型需向左旋转(以右子节点为轴),LL型需向右旋转(以左子节点为轴);
• 平衡因子处理对称:两者旋转后,原失衡节点与旋转轴节点的平衡因子均重置为0。
这种对称性是AVL树旋转操作的核心特征,掌握对称逻辑可显著降低对复杂调整策略的理解难度。通过左旋转处理RR型失衡,AVL树能够在保持二叉搜索树特性的同时,确保树高始终维持在O(log n)级别,从而保障插入、删除和查询操作的高效性。
(三)LR型失衡与先左后右旋转操作
LR型失衡是AVL树中一种典型的复合失衡场景,发生于在节点的左子树的右子树插入新节点后,导致该节点的平衡因子变为2。这种失衡无法通过单次旋转解决,需通过先左后右的复合旋转调整结构,同时精确更新相关节点的平衡因子以恢复树的平衡性。
失衡场景与旋转必要性
当失衡节点A的左子节点为B,B的右子节点为C(即插入路径为A→B→C的子树)时,A的平衡因子变为2,B的平衡因子可能为-1或0(取决于插入位置)。此时直接对A执行右旋转无法完全平衡树结构,需先通过左旋转调整B的右子树,再对A执行右旋转,使C成为新的根节点,从而实现整体平衡。
分步旋转过程
第一步:对B执行左旋转
以B为旋转中心,将C的左子树作为B的右子树,C成为B的父节点。此时B的右子树高度降低,为后续右旋转创造条件。
第二步:对A执行右旋转
以A为旋转中心,将C的右子树作为A的左子树,C成为A的父节点。此时C成为新的根节点,A和B分别作为其右子树和左子树,树的整体高度恢复平衡。
旋转步骤总结
1. 左旋转B:B->right = C->left;C->left = B
2. 右旋转A:A->left = C->right;C->right = A
3. 新根节点:C成为旋转后子树的根节点
平衡因子更新规则
平衡因子的更新需根据C的原始平衡因子(插入前的值)确定,具体规则如下:
| C的平衡因子(C->bf) | A的平衡因子(A->bf) | B的平衡因子(B->bf) | C的平衡因子(C->bf) |
| 1 | -1 | 0 | 0 |
| -1 | 0 | 1 | 0 |
| 0 | 0 | 0 | 0 |
规则解析:
• 若C->bf=1:插入发生在C的右子树,导致A的右子树相对高度增加,故A->bf=-1;B的左右子树恢复平衡,B->bf=0。
• 若C->bf=-1:插入发生在C的左子树,导致B的左子树相对高度增加,故B->bf=1;A的左右子树恢复平衡,A->bf=0。
• 若C->bf=0:插入未改变C的子树高度,A和B的平衡因子均重置为0。
• 无论何种情况,C作为新根节点,其平衡因子始终重置为0。
代码实现与逻辑解析
以下是LR型失衡修复的C++代码实现,关键步骤已添加注释:
AVLNode* leftRightRotate(AVLNode* A) {
AVLNode* B = A->left; // 获取A的左子节点B
AVLNode* C = B->right; // 获取B的右子节点C(失衡关键节点)
// 第一步:对B执行左旋转
B->right = C->left; // C的左子树成为B的右子树
C->left = B; // B成为C的左子节点
// 第二步:对A执行右旋转
A->left = C->right; // C的右子树成为A的左子树
C->right = A; // A成为C的右子节点
// 根据C的原始bf值更新平衡因子
if (C->bf == 1) {
A->bf = -1; // 插入C的右子树:A右子树变高
B->bf = 0; // B左右子树平衡
} else if (C->bf == -1) {
A->bf = 0; // A左右子树平衡
B->bf = 1; // 插入C的左子树:B左子树变高
} else { // C->bf == 0(插入后C子树高度未变)
A->bf = 0;
B->bf = 0;
}
C->bf = 0; // 新根C平衡因子重置为0
return C; // 返回新根节点C
}
代码核心逻辑:通过两次旋转(先左后右)将C提升为新根,同时根据C的原始平衡因子精确调整A和B的平衡因子,确保旋转后子树满足AVL树的平衡条件。分步旋转的必要性在于:仅通过单次旋转无法同时消除A和B的失衡,需先通过左旋转修正B的右子树高度,再通过右旋转修正A的左子树高度。
通过上述步骤,LR型失衡的AVL树可恢复结构平衡,且所有节点的平衡因子均满足[-1, 1]的约束条件。
(四)RL型失衡与先右后左旋转操作
RL型失衡是AVL树中一种典型的非对称失衡场景,发生于节点的右子树的左子树(Right-Left)插入新节点后,导致该节点的平衡因子变为-2。与LR型失衡(左子树的右子树插入)形成对称关系,其修复需通过先右后左的两步旋转操作实现结构调整,并根据中间节点的平衡因子精确更新相关节点的平衡因子。
失衡场景与旋转步骤
以失衡节点A为起点,其右子节点为B,B的左子节点为C(即插入路径为A→B→C的左或右子树)。此时A的平衡因子为-2,需通过以下两步旋转恢复平衡:
1. 第一步:右旋转B
对失衡节点A的右子节点B执行右旋转,将C提升为B的父节点。此时B的左子树变为C的右子树,C的右子树变为B。
2. 第二步:左旋转A
对失衡节点A执行左旋转,将C提升为新的根节点。此时A的右子树变为C的左子树,C的左子树变为A。
通过上述操作,新根节点C将原右子树的左分支转化为平衡的左右子树,使整棵子树的高度降低1,恢复AVL树的平衡性。
平衡因子更新规则
旋转后需根据中间节点C的平衡因子(插入前的值)更新A和B的平衡因子,C的平衡因子始终重置为0。具体规则如下:
| C的平衡因子(插入前) | A的平衡因子(更新后) | B的平衡因子(更新后) | C的平衡因子(更新后) |
| 1 | 0 | -1 | 0 |
| -1 | 1 | 0 | 0 |
| 0 | 0 | 0 | 0 |
规则解析:
• 当C的bf=1时,说明插入操作发生在C的右子树,导致B的左子树高度增加,因此B需调整为-1以反映右倾;A的左右子树高度恢复平衡,bf=0。
• 当C的bf=-1时,插入操作发生在C的左子树,导致A的右子树高度相对降低,因此A需调整为1以反映左倾;B的左右子树高度恢复平衡,bf=0。
• 当C的bf=0时,插入操作未改变C的左右子树高度差,A和B的平衡因子均重置为0。
代码实现与逻辑解析
以下是RL型失衡修复的C++代码实现,核心包含旋转操作与平衡因子更新逻辑:
AVLNode* rightLeftRotate(AVLNode* A) {
AVLNode* B = A->right; // A的右子节点B
AVLNode* C = B->left; // B的左子节点C(中间节点)
// 第一步:对B执行右旋转
B->left = C->right; // B的左子树指向C的右子树
C->right = B; // C的右子树指向B
// 第二步:对A执行左旋转
A->right = C->left; // A的右子树指向C的左子树
C->left = A; // C的左子树指向A
// 根据C的bf值更新A和B的平衡因子
if (C->bf == 1) {
A->bf = 0;
B->bf = -1;
} else if (C->bf == -1) {
A->bf = 1;
B->bf = 0;
} else { // C->bf == 0
A->bf = 0;
B->bf = 0;
}
C->bf = 0; // C的平衡因子始终重置为0
return C; // C成为新的子树根节点
}
代码中,B->left = C->right与C->right = B完成对B的右旋转,A->right = C->left与C->left = A完成对A的左旋转。平衡因子更新部分通过条件分支严格遵循前述规则,确保旋转后各节点平衡因子的准确性。
与LR型失衡的对称性对比
RL型与LR型失衡作为AVL树中两种非对称失衡场景,其修复操作呈现高度对称性:
| 维度 | LR型失衡(左子树的右子树插入) | RL型失衡(右子树的左子树插入) |
| 失衡路径 | A→左子树B→B的右子树C | A→右子树B→B的左子树C |
| 旋转步骤 | 先左旋转B,再右旋转A | 先右旋转B,再左旋转A |
| 平衡因子更新 | C的bf=1时,A=1、B=0;C的bf=-1时,A=0、B=-1 | C的bf=1时,A=0、B=-1;C的bf=-1时,A=1、B=0 |
这种对称性源于AVL树的左右结构镜像特性,掌握其中一种失衡的修复逻辑即可通过对称思维推导另一种,显著降低理解与记忆成本。
关键结论:RL型失衡的修复核心在于通过“右旋转中间节点→左旋转失衡节点”的组合操作,将非对称的右左子树转化为对称结构,同时依据中间节点的初始平衡因子精准更新相关节点的平衡因子,确保树结构的平衡性与高度最优性。
五、AVL树插入操作:流程与平衡调整
AVL 树的插入操作是维持其平衡特性的核心环节,通过 insertAVL 函数实现递归插入与动态平衡调整的联动。该过程包含递归查找插入位置、创建新节点、更新平衡因子、检测失衡状态及执行旋转操作等关键步骤,最终确保树的高度差不超过 1。
(一)插入流程的核心逻辑
AVL 树插入操作以递归方式实现,其核心控制流围绕 插入位置查找、平衡因子更新 和 失衡修复 三个层次展开:
1. 递归基线与关键字冲突处理
• 空树插入:当递归至空节点(root == nullptr)时,创建新节点存储关键字,同时将 taller 标志设为 true(表示此插入操作导致子树高度增加),并返回新节点作为当前子树的根。
• 关键字冲突:若待插入关键字已存在(key == root->key),则不执行插入,直接将 taller 设为 false(子树高度不变)并返回当前节点。
2. 左子树插入与平衡调整
当待插入关键字小于当前节点关键字(key < root->key)时,递归插入左子树。插入后根据 taller 标志(左子树是否增高)及当前节点的平衡因子(root->bf)进行分类处理:
左子树插入后的平衡因子调整规则
• 原平衡因子为 1:左子树原本已高于右子树,插入后左子树高度进一步增加,导致失衡。需根据左子节点的平衡因子判断旋转类型:
◦ 左子节点 bf == 1:执行 LL 型旋转(右单旋);
◦ 左子节点 bf == -1:执行 LR 型旋转(先左旋后右旋)。
• 原平衡因子为 0:插入后左子树高度超过右子树,平衡因子更新为 1,taller 保持 true(子树整体高度增加)。
• 原平衡因子为 -1:插入后左右子树高度趋于平衡,平衡因子更新为 0,taller 设为 false(子树高度不再增加)。
3. 右子树插入与平衡调整
当待插入关键字大于当前节点关键字(key > root->key)时,递归插入右子树,处理逻辑与左子树对称,平衡因子调整规则如下:
右子树插入后的平衡因子调整规则
• 原平衡因子为 -1:右子树原本已高于左子树,插入后右子树高度进一步增加,导致失衡。需根据右子节点的平衡因子判断旋转类型:
◦ 右子节点 bf == -1:执行 RR 型旋转(左单旋);
◦ 右子节点 bf == 1:执行 RL 型旋转(先右旋后左旋)。
• 原平衡因子为 0:插入后右子树高度超过左子树,平衡因子更新为 -1,taller 保持 true。
• 原平衡因子为 1:插入后左右子树高度趋于平衡,平衡因子更新为 0,taller 设为 false。
(二)代码实现与旋转逻辑解析
insertAVL 函数通过递归调用与条件分支实现上述逻辑,核心代码如下:
AVLNode* insertAVL(AVLNode* root, int key, bool& taller) {
if (root == nullptr) {
taller = true; // 空树插入后高度增加
return new AVLNode(key);
}
if (key == root->key) {
taller = false; // 关键字已存在,不插入
return root;
}
if (key < root->key) { // 插入左子树
root->left = insertAVL(root->left, key, taller);
if (taller) { // 左子树高度增加,需调整平衡因子
switch (root->bf) {
case 1: // 原左子树更深,插入后失衡
if (root->left->bf == 1)
root = rightRotate(root); // LL 型旋转
else
root = leftRightRotate(root); // LR 型旋转
taller = false; // 旋转后子树高度恢复,不再增加
break;
case 0: // 原平衡,插入后左高右低
root->bf = 1;
taller = true; // 子树高度增加
break;
case -1: // 原右子树更深,插入后平衡
root->bf = 0;
taller = false; // 子树高度不变
break;
}
}
} else { // 插入右子树(逻辑对称)
root->right = insertAVL(root->right, key, taller);
if (taller) { // 右子树高度增加,需调整平衡因子
switch (root->bf) {
case -1: // 原右子树更深,插入后失衡
if (root->right->bf == -1)
root = leftRotate(root); // RR 型旋转
else
root = rightLeftRotate(root); // RL 型旋转
taller = false;
break;
case 0: // 原平衡,插入后右高左低
root->bf = -1;
taller = true;
break;
case 1: // 原左子树更深,插入后平衡
root->bf = 0;
taller = false;
break;
}
}
}
return root;
}
旋转操作的选择逻辑
代码中通过嵌套条件判断实现旋转类型的精准选择:
• LL 型失衡:左子树的左子树插入导致失衡(root->bf == 1 且 root->left->bf == 1),执行右单旋(rightRotate);
• LR 型失衡:左子树的右子树插入导致失衡(root->bf == 1 且 root->left->bf == -1),执行先左旋后右旋(leftRightRotate);
• RR 型失衡:右子树的右子树插入导致失衡(root->bf == -1 且 root->right->bf == -1),执行左单旋(leftRotate);
• RL 型失衡:右子树的左子树插入导致失衡(root->bf == -1 且 root->right->bf == 1),执行先右旋后左旋(rightLeftRotate)。
(三)插入示例与树结构演化
以关键字序列 {13, 24, 37, 90, 53} 的插入过程为例,模拟 AVL 树的结构变化与平衡调整机制:
步骤 1:插入 13
• 创建根节点 13,平衡因子 bf = 0,taller = true。
树结构:[13(bf=0)]
步骤 2:插入 24(>13,右子树)
• 插入 24 作为 13 的右子节点,根节点 13 的 bf 更新为 -1(右子树高),taller = true。
树结构:
13(bf=-1)
\
24(bf=0)
步骤 3:插入 37(>24,右子树的右子树)
• 插入 37 后,24 的 bf 变为 -1,导致根节点 13 的 bf 变为 -2(失衡)。
• 触发 RR 型旋转(左单旋):以 24 为新根,13 为左子树,37 为右子树。
• 旋转后各节点 bf 均更新为 0,taller = false。
树结构:
24(bf=0)
/ \
13(bf=0) 37(bf=0)
步骤 4:插入 90(>37,右子树的右子树)
• 插入 90 作为 37 的右子节点,37 的 bf 变为 -1,24 的 bf 变为 -1(右子树高),taller = true。
树结构:
24(bf=-1)
/ \
13(bf=0) 37(bf=-1)
\
90(bf=0)
步骤 5:插入 53(<90 且 >37,右子树的左子树)
• 插入 53 作为 90 的左子节点,90 的 bf 变为 1(左子树高),导致 37 的 bf 变为 1(左子树高)。
• 根节点 24 的右子节点 37 的 bf = 1,且根节点 bf = -1,触发 RL 型旋转(先右旋后左旋):
1. 右旋:以 53 为轴,37 成为 53 的左子树,90 成为 53 的右子树;
2. 左旋:以 53 为新根,24 为左子树(含 13),90 为右子树。
• 旋转后各节点 bf 均更新为 0,taller = false。
最终树结构:
53(bf=0)
/ \
24(bf=0) 90(bf=0)
/ \
13(bf=0) 37(bf=0)
(四)总结
AVL 树的插入操作通过 递归插入-平衡因子更新-失衡检测-旋转修复 的闭环机制,确保树始终维持平衡状态。taller 标志作为高度变化的传递信号,协调父子节点的平衡因子调整;而旋转操作则通过局部结构重组,将失衡子树的高度差恢复至允许范围。这种联动机制使 AVL 树在动态插入过程中仍能保持 O(log n) 的查找效率。
六、AVL树删除操作:流程与平衡调整
AVL树删除操作是维持树结构平衡的关键环节,其复杂性主要体现在删除节点后可能引发的连锁失衡及多级平衡调整。该操作通过deleteAVL函数实现,整体流程包括递归查找目标节点、执行节点删除、更新平衡因子与高度状态、触发旋转调整四个核心阶段,其中平衡因子(BF)的动态更新与shorter标志的传递机制是确保树结构平衡的关键技术。
(一)递归查找与定位删除节点
删除操作的首要步骤是通过递归遍历定位目标节点。根据二叉搜索树(BST)的基本性质,递归逻辑如下:若目标key小于当前节点root->key,则递归进入左子树执行删除;若key大于root->key,则进入右子树;若相等则表示找到目标节点,进入删除执行阶段。这一过程确保了删除操作遵循BST的有序性,同时为后续平衡调整提供了自底向上的回溯路径。
(二)节点删除的三种场景处理
当定位到目标节点后,需根据节点的子树结构执行不同的删除策略,具体分为以下三种场景:
1. 叶子节点或单子节点删除
若目标节点为叶子节点(左右子树均为空)或仅存在单个子节点,处理逻辑较为直接:直接移除该节点,并将其唯一子节点(若存在)替换至当前位置。此时,树的高度必然减小,因此需将shorter标志设为true,以通知上层节点进行平衡因子更新。例如,删除仅有右子节点的节点时,直接用右子节点替换当前节点,原节点内存被释放,树高减1。
2. 双子节点删除(前驱替换法)
当目标节点同时存在左右子树时,需采用前驱替换策略:首先查找当前节点左子树中的最大值节点(即前驱节点,该节点必然是叶子节点或单子节点),将其key值替换至目标节点,随后递归删除该前驱节点。这一处理的核心优势是将双子节点删除转化为叶子节点或单子节点删除,确保BST性质不被破坏,同时通过递归回溯实现平衡因子的逐层更新。代码中通过findMaxNode(root->left)定位前驱节点,并通过root->left = deleteAVL(root->left, temp->key, shorter)递归删除,形成“替换-删除-回溯”的完整链路。
双子节点删除关键逻辑:
1. 查找左子树最大值节点(前驱):通过findMaxNode函数实现,沿左子树右分支遍历至末端;
2. 替换key值:仅交换节点值,不改变树结构,避免大规模节点移动;
3. 递归删除前驱:将问题转化为简单节点删除,确保后续平衡调整可复用现有逻辑。
(三)平衡调整:基于shorter标志与BF值的旋转决策
删除操作后,树高可能减小(shorter=true),需沿递归回溯路径更新平衡因子并检测失衡。平衡调整的核心逻辑通过switch-case语句实现,根据当前节点的BF值与子树高度变化状态(shorter)决定是否触发旋转,具体分为左子树删除后调整与右子树删除后调整两种对称场景。
shorter标志的传递机制
shorter是一个布尔值,用于标记当前子树是否因删除操作导致高度减小。其传递规则如下:
• 叶子节点/单子节点删除后,shorter直接设为true;
• 双子节点删除后,shorter的值由递归删除前驱节点的结果决定;
• 平衡调整过程中,若旋转操作未改变子树高度(如当root->right->bf=0时执行RR旋转),shorter需重置为false,终止向上传递。
左子树删除后的平衡调整
当删除操作发生在左子树(即通过root->left = deleteAVL(...)递归调用)且shorter=true时,当前节点的BF值将发生变化,可能触发以下调整:
• 原BF=1(左重):左子树高度减小后,左右子树高度平衡,BF更新为0,shorter保持true(树高仍可能继续减小);
• 原BF=0(平衡):左子树高度减小后,右子树相对变高,BF更新为-1(右重),shorter设为false(树高不再减小);
• 原BF=-1(右重):此时树结构失衡,需根据右子节点的BF值执行旋转:
◦ 若右子节点BF=-1(右子树右重):执行RR旋转(左单旋);
◦ 若右子节点BF=1(右子树左重):执行RL旋转(右左双旋)。
右子树删除后的平衡调整
与左子树删除对称,当删除操作发生在右子树且shorter=true时,调整逻辑如下:
• 原BF=-1(右重):右子树高度减小后,BF更新为0,shorter保持true;
• 原BF=0(平衡):右子树高度减小后,左子树相对变高,BF更新为1(左重),shorter设为false;
• 原BF=1(左重):树结构失衡,根据左子节点BF值执行旋转:
◦ 若左子节点BF=1(左子树左重):执行LL旋转(右单旋);
◦ 若左子节点BF=-1(左子树右重):执行LR旋转(左右双旋)。
旋转选择决策表(右子树删除后BF=1时):
| 左子节点BF值 | 旋转类型 | 调整后BF变化(root/左子/右子) |
| 1 | LL旋转(右单旋) | 0/0/0 |
| -1 | LR旋转(左右双旋) | 0/0/0 |
(四)代码逻辑与执行示例分析
以deleteAVL函数实现为例,通过具体删除场景模拟树结构变化与平衡调整过程,可更直观理解上述逻辑。以下结合代码中隐含的删除案例(如delKey=24和delKey=37)进行推演:
案例1:删除叶子节点(delKey=24,假设为右子树叶子节点)
1. 查找阶段:因24 > root->key,递归进入右子树,直至定位到目标节点;
2. 执行删除:目标节点为叶子节点(左右子树为空),直接删除并置shorter=true;
3. 回溯调整:
◦ 父节点BF原为-1(右重),因右子树高度减小,触发失衡检测;
◦ 右子节点BF=-1(原右子树为叶子节点),执行RR旋转(左单旋);
◦ 旋转后父节点BF更新为0,shorter设为true,继续向上回溯直至根节点。
案例2:删除双子节点(delKey=37,假设为左重节点)
1. 查找阶段:定位到目标节点,发现其存在左右子树;
2. 前驱替换:查找左子树最大值节点(假设为29),替换37为29,递归删除节点29;
3. 递归删除前驱:节点29为单子节点(左子树为空),删除后shorter=true;
4. 平衡调整:
◦ 原节点BF=1(左重),左子树高度减小后BF更新为0,shorter=true;
◦ 向上回溯至根节点,BF值未触发失衡,调整结束。
(五)关键技术细节与注意事项
1. BF值更新的时序性:平衡因子必须在递归回溯过程中自底向上更新,确保每个节点的BF值反映最新子树高度差;
2. 旋转操作对shorter的影响:当旋转后子树高度未恢复(如删除节点位于树的中间层且旋转后高度不变),需及时将shorter设为false,避免无效的上层调整;
3. 双子节点删除的递归陷阱:替换前驱节点后,必须递归删除该前驱,而非直接修改指针,否则会破坏AVL树的递归平衡调整链路。
通过上述流程的协同作用,AVL树删除操作能够在O(log n)时间复杂度内完成节点移除与结构平衡,确保树高始终维持在log₂(n+1)水平,为高效的动态查找提供保障。
七、遍历操作:实现与二叉搜索树性质验证
(一)中序遍历
中序遍历是二叉树遍历中的核心方法之一,其访问规则严格遵循“左-根-右”的顺序,即先递归遍历左子树,再访问当前节点,最后递归遍历右子树。这一特性使其成为验证二叉搜索树(BST)性质的关键工具——对于合法的二叉搜索树,中序遍历输出的节点关键字序列必然呈现严格递增的特征。
递归实现原理
中序遍历的递归实现通过函数自调用来完成节点访问顺序的控制。其核心逻辑如下:当当前节点不为空时,首先递归处理左子树,待左子树遍历完成后访问当前节点,最后递归处理右子树。这种深度优先的遍历方式确保了二叉搜索树中左子树节点值均小于根节点,右子树节点值均大于根节点的性质能够通过序列直观体现。
中序遍历递归实现代码
void inOrderTraverse(AVLNode* root) {
if (root != nullptr) { // 递归终止条件:当前节点为空
inOrderTraverse(root->left); // 第一步:遍历左子树
cout << root->key << " "; // 第二步:访问当前节点(输出关键字)
inOrderTraverse(root->right); // 第三步:遍历右子树
}
}
二叉搜索树性质验证实例
在AVL树(平衡二叉搜索树)的构建过程中,中序遍历可用于实时验证树结构是否满足二叉搜索树的升序性质。例如,向AVL树中依次插入关键字序列 {13, 24, 37, 90, 53} 后,通过中序遍历得到的输出序列为 13 24 37 53 90,该结果严格遵循升序排列,直接验证了插入操作后树结构仍保持二叉搜索树的核心特性。这一过程的本质是通过中序遍历将树结构“线性化”,将二维的树状关系转化为一维的有序序列,从而简化性质验证流程。
验证逻辑:二叉搜索树中任意节点的左子树所有节点值 < 该节点值 < 右子树所有节点值。中序遍历通过“左-根-右”的访问顺序,自然将这一性质转化为序列的升序特征。上述示例中,插入序列虽包含非顺序元素(如53在90之后插入),但AVL树的平衡调整机制(如旋转操作)仅改变树的形态,不影响中序遍历序列的递增性,进一步印证了中序遍历作为性质验证工具的可靠性。
综上,中序遍历不仅是二叉树结构分析的基础工具,更是确保AVL树等平衡搜索树正确性的关键验证手段,其递归实现简洁高效,验证逻辑直观且严谨。
(二)前序遍历与后序遍历
在 AVL 树的节点访问操作中,前序遍历与后序遍历是两种基于不同访问优先级的深度优先遍历方式,其核心差异体现在根节点的访问时机上,这种差异直接影响遍历结果的序列特征。
前序遍历:根节点优先访问策略
前序遍历遵循“根-左-右”的访问顺序,即首先访问当前节点(根节点),然后递归遍历其左子树,最后递归遍历其右子树。这种策略确保根节点在其子树节点之前被处理,适用于需要优先获取树结构顶层信息的场景。其代码实现通过将节点值输出语句置于左右子树递归调用之前来实现这一逻辑:
void preOrderTraverse(AVLNode* root) {
if (root != nullptr) {
cout << root->key << " "; // 根节点优先输出
preOrderTraverse(root->left); // 递归左子树
preOrderTraverse(root->right); // 递归右子树
}
}
后序遍历:子树节点优先访问策略
后序遍历则采用“左-右-根”的访问顺序,即先递归遍历左子树,再递归遍历右子树,最后访问当前节点(根节点)。这种策略确保所有子树节点均被处理后才处理根节点,常用于需要基于子树结果汇总计算根节点信息的场景(如销毁树结构、计算子树高度等)。其代码实现通过将节点值输出语句置于左右子树递归调用之后来实现:
void postOrderTraverse(AVLNode* root) {
if (root != nullptr) {
postOrderTraverse(root->left); // 递归左子树
postOrderTraverse(root->right); // 递归右子树
cout << root->key << " "; // 根节点最后输出
}
}
核心差异对比:两种遍历方式的本质区别在于根节点输出语句的位置。前序遍历中 cout << root->key 位于左右子树递归之前,而后序遍历中该语句位于左右子树递归之后,这种顺序差异直接导致了遍历序列的逆序特征——前序序列的第一个元素是整棵树的根节点,后序序列的最后一个元素是整棵树的根节点。
遍历结果差异的实例分析
以同一棵 AVL 树为例,若插入节点后形成的树结构使得前序遍历输出为“37 13 24 90 53”,则可推断该树的结构特征为:根节点为 37,其左子树以 13 为根(13 的左子树为 24),右子树以 90 为根(90 的左子树为 53)。按照前序“根-左-右”的顺序,访问路径为 37(根)→13(左子树根)→24(13 的左子树)→90(右子树根)→53(90 的左子树),从而形成该序列。
对应地,后序遍历输出“24 13 53 90 37”则反映了“左-右-根”的访问路径:先访问最左侧叶节点 24(13 的左子树),再访问其根节点 13(左子树完成);接着访问右子树的最左侧叶节点 53(90 的左子树),再访问其根节点 90(右子树完成);最后访问整棵树的根节点 37。这一结果直观展示了根节点访问时机对遍历序列的决定性影响。
(三)层次遍历
层次遍历(广度优先遍历)是 AVL 树中按层次从左到右访问节点的遍历方式,其非递归实现依赖队列的数据结构来保证访问顺序的正确性。队列的先进先出(FIFO)特性是实现“按层访问”的核心,通过依次将每层节点入队并按顺序出队处理,确保了节点访问严格遵循从上到下、从左到右的层次顺序。
实现逻辑与代码解析
层次遍历的代码实现如下,其核心逻辑围绕队列的初始化、节点入队/出队及子节点处理展开:
void levelOrderTraverse(AVLNode* root) {
if (root == nullptr) return; // 空树直接返回
queue<AVLNode*> q; // 初始化队列
q.push(root); // 根节点入队,启动遍历
while (!q.empty()) { // 队列非空时循环处理
AVLNode* curr = q.front();// 获取队首节点
q.pop(); // 弹出队首节点(已访问)
cout << curr->key << " "; // 访问当前节点(输出键值)
// 左子节点非空则入队,保证下一层从左到右访问
if (curr->left != nullptr) q.push(curr->left);
// 右子节点非空则入队,与左子节点形成层次顺序
if (curr->right != nullptr) q.push(curr->right);
}
}
队列操作关键步骤:
1. 初始化:将根节点入队,作为遍历的起点;
2. 循环处理:通过 q.front() 获取当前层节点,q.pop() 移除已访问节点;
3. 子节点入队:按左→右顺序将当前节点的子节点入队,确保下一层节点按顺序等待访问。
此过程通过队列的 FIFO 特性严格维持层次顺序,避免跨层访问。
实例分析:层次遍历结果的生成过程
以插入节点后层次遍历输出“37 13 90 24 53”为例,该结果直接反映了 AVL 树的层次结构:37 为根节点(第一层),13 和 90 为第二层节点(根的左、右子节点),24 和 53 为第三层节点(分别为 13 的右子节点和 90 的左子节点)。其生成过程如下:
1. 初始状态:队列 q = [37],队首节点为 37;
2. 第一层处理:弹出 37 并输出,入队其左子节点 13 和右子节点 90,队列变为 [13, 90];
3. 第二层处理:弹出 13 并输出,入队其右子节点 24(左子节点为空),队列变为 [90, 24];弹出 90 并输出,入队其左子节点 53(右子节点为空),队列变为 [24, 53];
4. 第三层处理:弹出 24 并输出(无子节点),队列变为 [53];弹出 53 并输出(无子节点),队列为空,遍历结束。
最终输出序列“37 13 90 24 53”完整呈现了树的层次结构,验证了层次遍历对 AVL 树结构的直观反映能力。
八、查找与辅助操作:功能实现与应用
查找与辅助操作是AVL树维护机制的关键组成部分,通过节点查找、最值定位、树高计算、节点计数及平衡性校验等功能,实现对树状态的实时监控与维护。这些操作不仅为AVL树的核心插入、删除等修改操作提供基础支持,还能直接反映树的结构特征与平衡状态,是确保AVL树性能的重要工具。
(一)searchAVL:节点查找功能
searchAVL基于二叉搜索树的基本原理实现节点查找,通过递归比较目标key值与当前节点key值的大小,决定向左或右子树深入,直至找到目标节点或遍历至空树。其核心逻辑遵循二叉搜索树的有序性:左子树所有节点key值小于根节点,右子树所有节点key值大于根节点。
核心逻辑:递归终止条件为当前节点为空(未找到目标)或当前节点key值等于目标key(找到目标);递归方向由key值比较结果决定(小于当前节点key则向左,否则向右)。
代码实现如下:
AVLNode* searchAVL(AVLNode* root, int key) {
if (root == nullptr || root->key == key) return root; // 终止条件:空树或找到目标
return key < root->key ? searchAVL(root->left, key) : searchAVL(root->right, key); // 递归方向选择
}
调用示例:假设现有AVL树包含节点{15, 30, 37, 45, 50},查找key=37的节点:
AVLNode* target = searchAVL(root, 37);
// 若树中存在37,target指向key=37的节点;否则返回nullptr
应用场景:在AVL树的删除操作前,需通过searchAVL定位目标节点;在数据检索场景中,可直接调用该函数查询指定key是否存在,例如用户查询某ID对应的记录是否存在于树中。
(二)findMinNode/findMaxNode:最值节点查找
findMinNode和findMaxNode分别用于定位AVL树中的最小key值节点和最大key值节点。根据二叉搜索树的性质,最小key值节点必为树的最左叶子节点(无左子树的节点),最大key值节点必为树的最右叶子节点(无右子树的节点)。两者均采用迭代实现以避免递归栈开销。
实现关键:通过while循环持续向左(findMinNode)或向右(findMaxNode)移动指针,直至子节点为空,此时当前节点即为最值节点。基线条件为空树时返回nullptr。
代码实现如下:
// 查找最小key值节点(最左节点)
AVLNode* findMinNode(AVLNode* root) {
if (root == nullptr) return nullptr; // 空树返回nullptr
while (root->left != nullptr) root = root->left; // 持续向左移动
return root;
}
// 查找最大key值节点(最右节点)
AVLNode* findMaxNode(AVLNode* node) {
if (node == nullptr) return nullptr; // 空树返回nullptr
while (node->right != nullptr) node = node->right; // 持续向右移动
return node;
}
调用示例:在包含节点{15, 30, 37, 45, 50}的AVL树中:
AVLNode* minNode = findMinNode(root); // 返回key=15的节点
AVLNode* maxNode = findMaxNode(root); // 返回key=50的节点
应用场景:在删除度为2的节点时,需通过findMinNode(查找右子树最小值)或findMaxNode(查找左子树最大值)获取前驱/后继节点以替换待删除节点;在范围查询中,可快速定位树中key的取值边界。
(三)getHeight:树高计算
getHeight函数通过递归方式计算AVL树中指定节点的高度,树高定义为从该节点到最深叶子节点的路径长度(边数+1)。其核心公式为:当前节点高度 = max(左子树高度, 右子树高度) + 1,基线条件为空树高度为0(无节点时路径长度为0)。
递归逻辑:对于非空节点,先递归计算左子树高度(leftHeight)和右子树高度(rightHeight),取两者最大值后加1即为当前节点高度;空节点直接返回0,避免递归无限进行。
代码实现如下:
int getHeight(AVLNode* node) {
if (node == nullptr) return 0; // 空树高度为0
int leftHeight = getHeight(node->left); // 递归计算左子树高度
int rightHeight = getHeight(node->right); // 递归计算右子树高度
return (leftHeight > rightHeight ? leftHeight : rightHeight) + 1; // 取最大高度+1
}
调用示例:计算上述包含5个节点的AVL树高度:
int treeHeight = getHeight(root); // 假设树为平衡状态,高度为3(路径:root→30→37,共2条边,高度3)
应用场景:getHeight是AVL树平衡维护的核心依赖函数,在插入/删除操作后,需通过该函数计算节点左右子树高度差,以判断是否需要执行旋转操作;同时,树高也是评估AVL树查询效率的重要指标(平衡树高约为log₂(n+1),确保O(log n)查询复杂度)。
(四)countNodes:节点总数统计
countNodes函数通过递归方式计算AVL树的总节点数,其逻辑基于树的结构分解:当前树的节点总数 = 左子树节点数 + 右子树节点数 + 1(当前节点),基线条件为空树节点数为0。
代码实现如下:
int countNodes(AVLNode* root) {
if (root == nullptr) return 0; // 空树节点数为0
return countNodes(root->left) + countNodes(root->right) + 1; // 左子树+右子树+当前节点
}
调用示例:统计上述5个节点的AVL树节点总数:
int totalNodes = countNodes(root); // 返回5,即树中所有节点的总和
应用场景:用于监控树的规模增长,例如在动态数据管理系统中,通过定期调用countNodes获取节点总数,判断是否需要进行数据分片或扩容;在性能测试中,可结合树高计算节点密度(树高/节点数),评估树的平衡质量。
(五)isBalanced:平衡性检查
isBalanced函数用于验证AVL树是否满足平衡性条件,即所有节点的左右子树高度差均不超过1。其检查逻辑包含双重条件:当前节点左右子树高度差≤1,且左子树和右子树均为平衡树(递归检查)。
平衡条件:对于任意节点,需同时满足:① |左子树高度 - 右子树高度| ≤ 1;② 左子树是平衡树;③ 右子树是平衡树。三者缺一不可,若任一节点不满足,则整棵树不平衡。
代码实现如下:
bool isBalanced(AVLNode* root) {
if (root == nullptr) return true; // 空树视为平衡
int leftHeight = getHeight(root->left); // 获取左子树高度
int rightHeight = getHeight(root->right); // 获取右子树高度
if (abs(leftHeight - rightHeight) > 1) return false; // 当前节点高度差超限,不平衡
return isBalanced(root->left) && isBalanced(root->right); // 递归检查左右子树
}
调用示例:检查上述平衡AVL树及插入失衡节点后的平衡性:
// 平衡树检查
bool balanced = isBalanced(root); // 返回true
// 插入节点60导致右子树高度差为2后检查
insertAVL(root, 60); // 假设插入后右子树高度超过左子树2
balanced = isBalanced(root); // 返回false,触发平衡维护机制
应用场景:在AVL树修改操作(插入/删除)后,需通过isBalanced验证树的平衡性是否被破坏,若返回false则需执行旋转调整;在树结构可视化工具中,可作为前置检查确保绘制的树符合AVL树定义。
综上,查找与辅助操作通过监控节点存在性、结构参数(高度、节点数)及平衡状态,为AVL树的稳定运行提供基础支撑。这些操作不仅是核心修改操作(插入/删除)的依赖组件,也是开发者分析树结构特征、优化性能的重要工具,共同确保AVL树在动态数据处理中维持高效的O(log n)操作复杂度。
九、高级操作:更新关键字与树复制
AVL 树的高级操作是对基础插入/删除功能的扩展,主要解决动态数据调整、结构备份与资源释放等实际需求,包括关键字更新(updateKey)、树结构复制(copyTree)和内存销毁(destroyAVLTree)。这些操作通过严谨的逻辑设计,确保在功能扩展的同时维持 AVL 树的平衡性与数据一致性。
(一)更新关键字(updateKey):动态调整节点值
在实际应用中,当需要修改已有节点的关键字(如学生成绩调整、商品价格更新)时,直接修改节点值可能破坏树的平衡特性(如导致左右子树高度差超过 1)。因此,updateKey 操作采用“先删除旧关键字,再插入新关键字”的两步策略,通过复用 deleteAVL 和 insertAVL 函数实现安全更新,并依赖删除操作返回的状态标志判断流程是否继续。
实现逻辑与代码解析
updateKey 函数的核心流程如下:
1. 删除旧关键字:调用 deleteAVL 函数移除旧关键字节点,通过引用参数 shorter 标记删除是否成功(删除成功时树高可能降低,shorter 设为 true)。
2. 插入新关键字:若删除成功(shorter 为 true),调用 insertAVL 函数插入新关键字,此时树高可能增加(通过 taller 标记),并返回 true;若删除失败(旧关键字不存在,shorter 为 false),直接返回 false。
代码示例如下:
bool updateKey(AVLNode*& root, int oldKey, int newKey) {
bool shorter = false, taller = false;
root = deleteAVL(root, oldKey, shorter); // 第一步:删除旧关键字
if (!shorter) { // 仅当删除成功时执行插入
root = insertAVL(root, newKey, taller); // 第二步:插入新关键字
return true;
}
return false; // 旧关键字不存在,更新失败
}
设计考量与注意事项
• 平衡维护:通过“删除+插入”组合操作,确保每次结构变更后均触发 AVL 树的平衡调整机制(旋转操作),避免直接修改关键字导致的失衡风险。
• 原子性判断:shorter 标志是判断更新成功的核心依据——只有当旧关键字确实存在并被删除时,才进行新关键字插入,保证操作的逻辑原子性。
应用场景:动态数据修正(如将学生成绩 53 分更新为 50 分)、实时系统参数调整等需保持数据有序性的场景。更新失败通常意味着旧关键字不存在,需在业务层处理异常。
(二)树复制(copyTree):深拷贝与结构独立
为实现 AVL 树的备份、快照或并行处理,需创建树的独立副本,此时浅拷贝(仅复制指针)会导致原树与副本共享节点内存,修改一方会影响另一方。copyTree 函数通过深拷贝(递归复制每个节点及子树)确保新树与原树完全独立。
实现逻辑与代码解析
copyTree 采用递归复制策略,流程如下:
1. 终止条件:若当前节点为空(root == nullptr),返回空指针。
2. 复制当前节点:创建新节点,复制原节点的关键字(key)和平衡因子(bf)。
3. 递归复制子树:分别递归复制左子树(left)和右子树(right),并赋值给新节点的对应指针。
代码示例如下:
AVLNode* copyTree(AVLNode* root) {
if (root == nullptr) return nullptr;
AVLNode* newNode = new AVLNode(root->key); // 复制当前节点关键字
newNode->bf = root->bf; // 复制平衡因子(维持结构平衡)
newNode->left = copyTree(root->left); // 递归复制左子树
newNode->right = copyTree(root->right); // 递归复制右子树
return newNode;
}
设计考量与注意事项
• 平衡因子复制:平衡因子(bf)是 AVL 树维持平衡的核心元数据,必须随节点一同复制,否则新树的平衡判断会失效。
• 内存独立性:新树节点通过 new 运算符分配内存,与原树无指针共享,支持独立修改与销毁。
应用场景:数据备份(如数据库索引快照)、多线程并行计算(避免锁竞争)、树结构对比分析(原树与副本差异化遍历)等。
(三)树销毁(destroyAVLTree):后序遍历与内存释放
当 AVL 树不再使用时,需释放所有节点内存以避免泄漏。destroyAVLTree 函数采用后序遍历(左→右→根)销毁节点,确保先释放子树内存,再删除当前节点,避免因提前删除父节点导致子树内存无法访问。
实现逻辑与流程解析
销毁流程如下:
1. 递归销毁左子树:调用 destroyAVLTree(root->left) 释放左子树所有节点。
2. 递归销毁右子树:调用 destroyAVLTree(root->right) 释放右子树所有节点。
3. 删除当前节点:释放根节点内存(delete root),并将指针置空(避免野指针)。
设计考量与注意事项
• 内存安全:后序遍历确保每个节点的子树均被完全销毁后,才释放当前节点,避免“悬垂指针”(指向已释放内存的指针)风险。
• 递归终止:当节点为空(root == nullptr)时直接返回,避免对空指针执行 delete 操作。
应用场景:程序退出前的资源清理、动态数据结构生命周期管理(如临时索引树的释放)、内存敏感型系统(如嵌入式设备)的内存回收。
(四)高级操作的协同应用
在实际工程中,三个操作常组合使用:例如,先通过 copyTree 创建原树备份,再使用 updateKey 动态调整数据,最后通过 destroyAVLTree 释放过期副本内存。这种“备份-修改-清理”模式既能保证数据安全性,又能维持系统资源高效利用。
综合示例流程
1. 复制原树:AVLNode* backup = copyTree(originalRoot);
2. 更新关键字:bool success = updateKey(originalRoot, 53, 50);(假设原树存在关键字 53)
3. 遍历验证:分别中序遍历 originalRoot(含 50)和 backup(含 53),确认副本独立。
4. 释放备份:destroyAVLTree(backup);
通过上述组合,实现了数据的安全更新与资源的可控管理,体现了 AVL 树高级操作在工程实践中的核心价值。
十、完整代码逐函数解析
(一)节点结构定义:`struct AVLNode`
函数功能:定义 AVL 树的基本节点结构,存储键值、平衡因子及子节点指针,是所有操作的基础数据单元。
参数说明:无(结构体定义),成员变量包括:
• int key:节点存储的键值(关键字);
• int bf:平衡因子(Balance Factor),取值范围为 -1、0、1,表征左右子树高度差;
• AVLNode* left/AVLNode* right:指向左右子节点的指针。
返回值意义:无(结构体定义)。
逻辑流程:通过构造函数初始化节点,默认bf=0(新节点为叶子节点,左右子树高度均为 0),left和right指针初始化为nullptr。
关键步骤:
struct AVLNode {
int key; // 键值
int bf; // 平衡因子(右子树高 - 左子树高)
AVLNode* left; // 左子节点指针
AVLNode* right; // 右子节点指针
// 构造函数:初始化键值,平衡因子为0,子指针为空
AVLNode(int k) : key(k), bf(0), left(nullptr), right(nullptr) {}
};
节点的构造函数确保新创建的节点初始状态满足 AVL 树的平衡要求(平衡因子为 0),为后续插入、删除操作的平衡调整提供基准。
(二)旋转函数:平衡调整的核心实现
1. 右旋转函数:`rightRotate(AVLNode* A)`(处理 LL 型失衡)
函数功能:通过右旋转调整 LL 型失衡(左子树的左子树过高),恢复树的平衡状态。
参数说明:A 为失衡节点(旋转前的根节点)。
返回值意义:旋转后的新根节点(原A的左子节点B)。
逻辑流程:
1. 保存A的左子节点B及B的右子树T2;
2. 调整指针关系:A的左子树指向T2,B的右子树指向A;
3. 重置平衡因子:A和B的平衡因子均更新为 0(LL 型失衡旋转后子树高度恢复平衡)。
关键步骤:
AVLNode* rightRotate(AVLNode* A) {
AVLNode* B = A->left; // B 为 A 的左子节点
AVLNode* T2 = B->right; // T2 为 B 的右子树
B->right = A; // B 的右子树指向 A
A->left = T2; // A 的左子树指向 T2
// 重置平衡因子(LL 型失衡旋转后平衡因子均为 0)
A->bf = 0;
B->bf = 0;
return B; // B成为新根
}
2. 左旋转函数:`leftRotate(AVLNode* A)`(处理 RR 型失衡)
函数功能——处理 RR 型失衡(右子树的右子树过高),逻辑与rightRotate对称;
关键差异:指针调整为A->right = B->left、B->left = A,平衡因子同样重置为 0。
3. 左右双旋转函数:`leftRightRotate(AVLNode* A)`(处理 LR 型失衡)
函数功能:通过先左旋转后右旋转处理 LR 型失衡(左子树的右子树过高)。
逻辑流程:
1. 对A的左子节点B执行leftRotate(转为 LL 型失衡);
2. 对A执行rightRotate完成平衡调整;
3. 根据旋转前子树高度更新平衡因子(A和B的bf需根据C的原bf值调整,如C->bf=1时A->bf=-1,B->bf=0)。
4. 右左双旋转函数:`rightLeftRotate(AVLNode* A)`(处理 RL 型失衡)
函数功能——处理 RL型失衡(右子树的左子树过高),逻辑与leftRightRotate对称,先右旋转后左旋转。
(三)插入操作:`insertAVL(AVLNode*& root, int key, bool& taller)`
函数功能:向 AVL 树插入新键值,通过递归实现,并在插入后检查平衡状态,必要时调用旋转函数调整,同时通过taller标志向上传递子树高度变化信息。
参数说明:
• root:当前子树的根节点(引用传递,支持修改父节点指针);
• key:待插入的键值;
• taller:输出型标志,标记当前子树是否因插入而增高(true表示增高,需向上传递平衡检查)。
返回值意义:插入操作后的子树新根(可能因旋转发生变化)。
逻辑流程:
1. 递归终止条件:若root为空,创建新节点并设置taller=true(新节点插入导致子树增高);
2._ 递归插入:若key < root->key,向左子树插入,反之向右子树插入;
2. 平衡调整:插入后若taller为true,根据root->bf值判断失衡类型:
◦ root->bf = 0→插入后变为左高(-1)或右高(1),taller保持true;
◦ root->bf = -1(原左高)→若左子树插入导致root->bf = -2,需根据左子树的bf值选择旋转类型(LL 型用rightRotate;LR 型用leftRightRotate),调整后taller=false(子树高度恢复);
◦ root->bf = 1(原右高)→类似左高情况,右子树插入导致root->bf = 2时触发 RR 或 RL 旋转。
关键逻辑:taller标志的传递机制是插入操作的核心。当新节点插入叶子位置时,taller初始为true,逐层向上传递;若某层因旋转调整使子树高度恢复原状态(如从失衡变为平衡),则将taller设为false,终止向上传递,避免无效的平衡检查。
(四)删除操作:`deleteAVL(AVLNode*& root, int key, bool& shorter)`
函数功能:从 AVL 树删除指定键值,通过递归实现,删除后通过shorter标志传递子树高度变化信息,并触发平衡调整。
参数说明:
• root:当前子树的根节点(引用传递);
• key:待删除的键值;
• shorter:输出型标志,标记当前子树是否因删除而变矮(true表示变矮,需向上传递平衡检查)。
返回值意义:删除操作后的子树新根。
逻辑流程:
1. 递归查找待删除节点:若key < root->key向左子树递归,反之向右子树递归;
2. 删除节点处理:
◦ 叶子节点或单支节点:直接删除并替换为子节点,设置shorter=true;
◦ 双支节点:调用findMaxNode(root->left)获取左子树最大值(前驱节点),替换当前节点的键值,再递归删除前驱节点(转化为单支或叶子节点删除);
3. 平衡调整:删除后若shorter为true,根据root->bf值判断失衡类型(与插入操作对称,需处理左低或右低导致的失衡),调用旋转函数调整后设置shorter为false或true(取决于调整后子树高度是否变化)。
函数依赖:删除双支节点时依赖findMaxNode函数获取前驱节点,形成“deleteAVL→findMaxNode”的调用链路。
(五)遍历函数:验证与结构展示
1. 中序遍历:`inOrderTraverse(AVLNode* root)`
函数功能:按“左-根-右”顺序遍历树,输出结果为升序序列,用于验证 AVL 树的二叉搜索树特性。
关键步骤:递归实现,若root非空,先遍历左子树,再输出root->key,最后遍历右子树。
2. 层次遍历:`levelOrderTraverse(AVLNode* root)`
函数功能:按层次(从上到下、从左到右)输出节点,直观展示树的结构形态,辅助验证平衡调整效果。
关键步骤:使用队列存储当前层节点,遍历一层后依次出队并将子节点入队,循环至队列为空。
(六)辅助函数:功能支持与状态验证
1. 最值查找:`findMinNode(AVLNode* root)`/`findMaxNode(AVLNode* root)`
函数功能:分别查找树中最小键值节点(最左节点)和最大键值节点(最右节点),为删除双支节点提供前驱/后继替换支持。
关键步骤:findMinNode通过while (root->left) root = root->left找到最左节点,findMaxNode通过while (root->right) root = root->right找到最右节点。
2. 平衡性检查:`isBalanced(AVLNode* root)`
函数功能:验证整棵树是否满足 AVL 平衡条件(所有节点平衡因子的绝对值≤1),辅助测试代码正确性。
关键步骤:递归检查每个节点的bf值,并通过getHeight函数验证平衡因子与实际子树高度差的一致性(bf = getHeight(right) - getHeight(left))。
(七)高级函数:功能扩展与内存管理
1. 键值更新:`updateKey(AVLNode*& root, int oldKey, int newKey)`
函数功能:更新指定键值,通过“先删除后插入”实现,避免直接修改键值导致的结构失衡。
调用链路:updateKey→deleteAVL(oldKey, shorter)→findMaxNode(如需前驱替换),删除完成后调用insertAVL(newKey, taller)→旋转函数(如需平衡调整),实现模块化复用。
2. 树拷贝与销毁:`copyTree(AVLNode* root)`/`destroyAVLTree(AVLNode*& root)`
函数功能:copyTree深拷贝一棵 AVL 树(递归复制每个节点,避免浅拷贝的指针共享问题);destroyAVLTree递归释放所有节点内存,防止内存泄漏。
(八)main 函数:测试流程与功能验证
函数功能:通过一系列测试用例验证 AVL 树的正确性,覆盖插入、删除、更新、遍历等核心操作。
测试流程:
1. 创建空树,插入测试键值(如 30, 20, 40, 10, 25),通过中序遍历验证升序特性,层次遍历观察平衡结构;
2. 删除节点(如删除 40),检查shorter标志传递及平衡调整效果(通过isBalanced验证平衡性);
3. 更新键值(如将 20 更新为 22),通过先删除后插入的调用链路实现,验证树结构仍保持平衡;
4. 测试边界情况:插入重复键值(应忽略)、删除不存在键值(应提示)、空树删除(安全处理);
5. 调用destroyAVLTree释放内存,完成测试。
(九)函数间依赖关系与模块化设计
AVL 树代码通过清晰的函数分工实现模块化设计,核心调用链路如下:
• 插入链路:insertAVL→旋转函数(rightRotate/leftRotate等)(平衡调整时);
• 删除链路:deleteAVL→findMaxNode(双支节点删除)→旋转函数(平衡调整时);
• 更新链路:updateKey→deleteAVL→insertAVL(复用删除和插入逻辑);
• 验证链路:main→inOrderTraverse/levelOrderTraverse→isBalanced(结果验证)。
这种设计确保每个函数专注单一职责,旋转函数仅负责结构调整,插入/删除函数专注核心操作与标志传递,辅助函数提供基础功能支持,共同构成完整的 AVL 树实现体系。
十一、测试用例分析与结果验证
为验证 AVL 树实现的正确性与可靠性,通过设计多组测试用例对插入、遍历、查找、结构信息、删除、更新及复制等核心操作进行系统性验证。以下结合具体测试场景与代码输出结果,展开详细分析。
(一)插入测试:平衡调整与结构验证
插入测试选取关键字数组 {13, 24, 37, 90, 53},通过跟踪插入过程及平衡调整机制,验证 AVL 树的动态平衡特性。插入顺序及调整过程如下:
1. 插入 13:树为空,13 成为根节点,树高 1,节点数 1。
2. 插入 24:作为 13 的右子节点,树高 2,节点数 2,左右子树高度差 1(平衡)。
3. 插入 37:作为 24 的右子节点,此时根节点 13 的右子树高度为 2,左子树高度 0,高度差 2(失衡)。触发 右单旋,以 24 为新根,13 为其左子节点,37 为其右子节点,树高恢复为 2,节点数 3。
4. 插入 90:作为 37 的右子节点,此时 24 的右子树(37)高度为 2,左子树(13)高度 1,高度差 1(平衡),树高 3,节点数 4。
5. 插入 53:作为 90 的左子节点,此时 37 的右子树(90)高度 1,左子树(空)高度 0,平衡;但 24 的右子树(37)高度 2,左子树(13)高度 1,仍平衡。最终树结构稳定,中序遍历结果为 "13 24 37 53 90",符合升序特性,验证插入操作及平衡调整的正确性。
(二)遍历测试:多维度结构确认
通过中序、前序、后序及层次遍历验证树结构的一致性。其中,中序遍历结果为 13 24 37 53 90,严格遵循二叉搜索树(BST)的升序排列规则,表明 AVL 树的 BST 性质未因平衡调整而破坏。其他遍历方式(如前序、后序)虽未在摘要中提供具体输出,但通过与中序遍历结果的交叉验证,可确认树的拓扑结构符合预期,即插入后的树以 24 或 37 为根(结合后续结构信息中树高 3 推断,根节点为 37,左子树 24(左 13),右子树 53(右 90))。
(三)查找测试:路径与前后继验证
以查找关键字 37 为例,验证查找逻辑及前驱、后继计算的准确性:
• 查找路径:从根节点(37)直接命中,无需遍历子树,查找成功。
• 前驱节点:定义为“左子树中的最大值”,37 的左子树为 24,其右子树为空,故最大值为 24。
• 后继节点:定义为“右子树中的最小值”,37 的右子树为 53,其左子树为空,故最小值为 53。
测试结果显示,查找 37 成功,前驱为 24,后继为 53,与理论推导完全一致,验证了查找算法的正确性。
(四)结构信息测试:量化指标验证
插入 5 个节点后,结构信息测试结果如下:
• 树高:3(根节点 37 到叶节点 13/90 的路径长度为 2,树高 = 路径长度 + 1 = 3)。
• 节点数:5(与插入的关键字总数一致)。
• 平衡性检查:isBalanced 函数返回 true,表明所有节点的左右子树高度差均 ≤ 1。
这些量化指标直接印证了 AVL 树在插入操作后的结构完整性与平衡性。
(五)删除测试:动态平衡维持
删除测试分为两步,验证删除操作后树的平衡性:
1. 删除 24:24 为根节点 37 的左子节点,且其左子树为 13(叶节点),右子树为空。删除后,37 的左子树变为 13,此时 37 的左右子树高度分别为 1(左)和 2(右,53-90),高度差 1(平衡)。中序遍历结果更新为 "13 37 53 90"。
2. 删除 37:37 为根节点,其左子树为 13,右子树为 53(右子树 90)。删除后,选择 后继节点 53 作为新根,13 为 53 的左子节点,90 为其右子节点,树高 2,节点数 3,左右子树高度差 1(平衡)。中序遍历结果更新为 "13 53 90"。
两次删除后,isBalanced 均返回 true,验证了删除操作中平衡调整机制的有效性。
(六)更新测试:删除+插入的原子性
更新操作通过“删除 53 + 插入 50”模拟,验证数据替换的正确性:
• 删除 53:53 为根节点(删除 37 后),其右子树为 90,左子树为 13。删除后,选择后继节点 90 为新根,13 为其左子节点,树高 2,中序遍历结果为 "13 90"。
• 插入 50:作为 90 的左子节点,此时 90 的左子树(50)高度 1,右子树高度 0,高度差 1(平衡),树高 2,节点数 3。中序遍历结果更新为 "13 50 90",表明更新操作成功维持了 BST 性质与平衡性。
(七)复制测试:独立性验证
复制测试通过创建原树的深拷贝,验证复制树与原树的独立性:
• 遍历一致性:复制树的中序遍历结果与原树完全一致(如原树删除 37 后中序为 "13 53 90",复制树结果相同)。
• 内存独立性:复制树的节点内存地址与原树无重叠,修改复制树不会影响原树结构,反之亦然。
这一结果验证了复制操作的深拷贝特性,确保数据隔离与安全性。
测试结论:所有测试用例的输出结果均与理论预期一致,表明 AVL 树的插入、删除、查找、更新、复制等核心操作实现正确,平衡调整机制有效,满足数据结构的可靠性要求。
十二、性能分析与应用场景
AVL 树作为最早实现的自平衡二叉搜索树,其性能特性与应用场景的精准把握对技术选型具有重要意义。本节将从时间复杂度、空间复杂度两个维度展开分析,并结合实际应用场景与局限性提供技术决策参考。
(一)时间复杂度分析
AVL 树的核心性能优势源于其严格的平衡性保证:通过平衡因子(bf) 控制与旋转操作的动态调整,树的高度始终保持在 O(log n) 级别(其中 n 为节点数量)。这一特性直接决定了其关键操作的时间复杂度:
• 查找操作:由于树高为 O(log n),最坏情况下只需遍历从根到叶的路径,时间复杂度为 O(log n),显著优于普通二叉搜索树在最坏情况下的 O(n)(如退化为链表时)。
• 插入与删除操作:插入或删除节点后可能破坏平衡性,需通过旋转操作(单次旋转为常数时间 O(1),单次操作最多需 O(1) 次旋转)恢复平衡。因此整体时间复杂度仍由树高主导,保持 O(log n)。
性能对比核心结论:AVL 树通过严格平衡策略将所有操作的时间复杂度稳定控制在 O(log n),解决了普通二叉搜索树在无序插入时的性能退化问题,为动态数据集合提供了稳定高效的访问能力。
(二)空间复杂度分析
AVL 树的空间开销主要来自节点存储:每个节点需包含 关键字(key)、平衡因子(bf) 及 左右孩子指针。对于包含 n 个节点的 AVL 树:
• 总空间复杂度:O(n),其中指针与关键字存储为二叉搜索树的基础开销,平衡因子(通常为一个整数,如 -1、0、1)仅增加常数级空间 overhead。
• 平衡因子的空间合理性:尽管每个节点额外存储平衡因子,但该字段仅占用少量空间(如 4 字节整数),却能通过提前预判失衡位置大幅降低平衡调整的时间成本,是典型的“空间换时间”优化策略,在大多数场景下具有极高的性价比。
(三)应用场景与技术选型
适用场景
AVL 树的 稳定 O(log n) 时间复杂度 使其特别适合以下场景:
• 数据库索引:需频繁执行范围查询与动态更新,AVL 树的严格平衡性可确保查询延迟稳定,避免极端情况下的性能抖动。
• 编译器符号表:在代码编译过程中,符号(变量、函数名)的插入、查找与删除操作频繁,AVL 树能高效维护符号的有序性与访问效率。
• 实时系统动态排序:如航空管制、工业控制等实时场景,要求操作响应时间可预测,AVL 树的稳定性能可满足实时性约束。
局限性与对比
AVL 树的主要局限在于 旋转操作开销:每次插入或删除最多可能触发 O(log n) 次旋转(实际为常数次,但实现复杂度较高),导致其在插入删除极频繁的场景(如高频交易系统的订单簿)中性能略低于红黑树——后者通过放宽平衡性要求(黑高平衡)减少旋转次数,在动态更新密集场景下表现更优。
技术选型建议:若应用以查询操作为主、对性能稳定性要求高(如数据库索引),AVL 树是理想选择;若插入删除操作远多于查询(如高频数据流处理),红黑树的“弱平衡”策略可能带来更优性能。
综上,AVL 树通过严格的平衡控制实现了稳定高效的动态数据管理能力,其性能特性使其在查找密集型场景中具有不可替代的优势,同时需根据实际操作频率权衡旋转开销对系统整体性能的影响。
十三、完整代码展示
(一)C++代码如下:
#include <iostream>
#include <queue>
#include <string>
using namespace std;
// AVL 树结点结构
struct AVLNode {
int key; // 关键字
int bf; // 平衡因子:左子树深度 - 右子树深度
AVLNode* left; // 左子指针
AVLNode* right; // 右子指针
AVLNode(int k) : key(k), bf(0), left(nullptr), right(nullptr) {}
};
// 声明函数
AVLNode* rightRotate(AVLNode* A); // LL 型:右旋转
AVLNode* leftRotate(AVLNode* A); // RR 型:左旋转
AVLNode* leftRightRotate(AVLNode* A); // LR 型:先左后右旋转
AVLNode* rightLeftRotate(AVLNode* A); // RL 型:先右后左旋转
AVLNode* insertAVL(AVLNode* root, int key, bool& taller); // 插入操作
AVLNode* deleteAVL(AVLNode*& root, int key, bool& shorter); // 删除操作
void inOrderTraverse(AVLNode* root); // 中序遍历
void destroyAVLTree(AVLNode*& root); // 销毁树
int getHeight(AVLNode* node); // 获取树的高度
AVLNode* findMaxNode(AVLNode* node); // 查找最大节点
// 新增操作的声明
AVLNode* searchAVL(AVLNode* root, int key); // 查找指定关键字
AVLNode* findMinNode(AVLNode* root); // 获取最小值节点
bool isBalanced(AVLNode* root); // 检查树是否平衡
int countNodes(AVLNode* root); // 计算节点总数
void preOrderTraverse(AVLNode* root); // 前序遍历
void postOrderTraverse(AVLNode* root); // 后序遍历
void levelOrderTraverse(AVLNode* root); // 层次遍历
bool updateKey(AVLNode*& root, int oldKey, int newKey); // 更新节点关键字
AVLNode* findPredecessor(AVLNode* node); // 查找前驱节点
AVLNode* findSuccessor(AVLNode* node); // 查找后继节点
AVLNode* copyTree(AVLNode* root); // 复制树
// 测试主函数
int main() {
AVLNode* root = nullptr;
bool taller = false, shorter = false;
// 1. 插入测试数据
int keys[] = {13, 24, 37, 90, 53};
int n = sizeof(keys) / sizeof(keys[0]);
cout << "插入的关键字序列:";
for (int i = 0; i < n; ++i) {
cout << keys[i] << " ";
root = insertAVL(root, keys[i], taller);
}
cout << "\n\n";
// 2. 遍历测试
cout << "插入后中序遍历(升序验证):";
inOrderTraverse(root);
cout << "\n插入后前序遍历:";
preOrderTraverse(root);
cout << "\n插入后后序遍历:";
postOrderTraverse(root);
cout << "\n插入后层次遍历:";
levelOrderTraverse(root);
cout << "\n\n";
// 3. 查找测试
int searchKey = 37;
AVLNode* found = searchAVL(root, searchKey);
if (found) {
cout << "查找关键字 " << searchKey << " 成功,节点值为 " << found->key << endl;
AVLNode* pred = findPredecessor(found);
AVLNode* succ = findSuccessor(found);
cout << " 前驱节点:" << (pred ? to_string(pred->key) : "不存在") << endl;
cout << " 后继节点:" << (succ ? to_string(succ->key) : "不存在") << endl;
} else {
cout << "查找关键字 " << searchKey << " 失败" << endl;
}
cout << endl;
// 4. 平衡性与结构信息测试
cout << "树是否平衡:" << (isBalanced(root) ? "是" : "否") << endl;
cout << "树的高度:" << getHeight(root) << endl;
cout << "节点总数:" << countNodes(root) << endl;
cout << "\n";
// 5. 删除测试
int delKey = 24;
root = deleteAVL(root, delKey, shorter);
cout << "删除 " << delKey << " 后中序遍历:";
inOrderTraverse(root);
cout << "\n删除后树是否平衡:" << (isBalanced(root) ? "是" : "否") << endl;
cout << "\n";
delKey = 37;
root = deleteAVL(root, delKey, shorter);
cout << "删除 " << delKey << " 后中序遍历:";
inOrderTraverse(root);
cout << "\n删除后树是否平衡:" << (isBalanced(root) ? "是" : "否") << endl;
cout << "\n";
// 6. 更新关键字测试
int oldKey = 53, newKey = 50;
if (updateKey(root, oldKey, newKey)) {
cout << "更新关键字 " << oldKey << " 为 " << newKey << " 成功,中序遍历:";
inOrderTraverse(root);
cout << endl;
} else {
cout << "更新关键字 " << oldKey << " 失败(不存在)" << endl;
}
cout << "更新后树是否平衡:" << (isBalanced(root) ? "是" : "否") << endl;
cout << "\n";
// 7. 复制树测试
AVLNode* copiedRoot = copyTree(root);
cout << "复制树的中序遍历:";
inOrderTraverse(copiedRoot);
cout << endl;
// 释放内存
destroyAVLTree(root);
destroyAVLTree(copiedRoot);
return 0;
}
// LL 型:右旋转
AVLNode* rightRotate(AVLNode* A) {
AVLNode* B = A->left;
A->left = B->right;
B->right = A;
// 更新平衡因子
A->bf = 0;
B->bf = 0;
return B; // B成为新根
}
// RR 型:左旋转
AVLNode* leftRotate(AVLNode* A) {
AVLNode* B = A->right;
A->right = B->left;
B->left = A;
// 更新平衡因子
A->bf = 0;
B->bf = 0;
return B; // B成为新根
}
// LR 型:先左旋转(子树),再右旋转(根)
AVLNode* leftRightRotate(AVLNode* A) {
AVLNode* B = A->left;
AVLNode* C = B->right;
// 第一步:左旋转B
B->right = C->left;
C->left = B;
// 第二步:右旋转A
A->left = C->right;
C->right = A;
// 更新平衡因子
if (C->bf == 1) {
A->bf = -1;
B->bf = 0;
} else if (C->bf == -1) {
A->bf = 0;
B->bf = 1;
} else { // C->bf == 0
A->bf = 0;
B->bf = 0;
}
C->bf = 0;
return C; // C成为新根
}
// RL 型:先右旋转(子树),再左旋转(根)
AVLNode* rightLeftRotate(AVLNode* A) {
AVLNode* B = A->right;
AVLNode* C = B->left;
// 第一步:右旋转B
B->left = C->right;
C->right = B;
// 第二步:左旋转A
A->right = C->left;
C->left = A;
// 更新平衡因子
if (C->bf == 1) {
A->bf = 0;
B->bf = -1;
} else if (C->bf == -1) {
A->bf = 1;
B->bf = 0;
} else { // C->bf == 0
A->bf = 0;
B->bf = 0;
}
C->bf = 0;
return C; // C成为新根
}
// AVL 树插入(含平衡调整)
AVLNode* insertAVL(AVLNode* root, int key, bool& taller) {
if (root == nullptr) {
taller = true; // 空树插入后变高
return new AVLNode(key);
}
if (key == root->key) {
taller = false; // 关键字已存在,不插入
return root;
}
if (key < root->key) { // 插入左子树
root->left = insertAVL(root->left, key, taller);
if (taller) { // 左子树高度增加,需调整平衡
switch (root->bf) {
case 1: // 原左子树更深,现在不平衡
if (root->left->bf == 1)
root = rightRotate(root); // LL型
else
root = leftRightRotate(root); // LR型
taller = false;
break;
case 0: // 原平衡,现在左子树高
root->bf = 1;
taller = true;
break;
case -1: // 原右子树更深,现在平衡
root->bf = 0;
taller = false;
break;
}
}
} else { // 插入右子树
root->right = insertAVL(root->right, key, taller);
if (taller) { // 右子树高度增加,需调整平衡
switch (root->bf) {
case -1: // 原右子树更深,现在不平衡
if (root->right->bf == -1)
root = leftRotate(root); // RR型
else
root = rightLeftRotate(root); // RL型
taller = false;
break;
case 0: // 原平衡,现在右子树高
root->bf = -1;
taller = true;
break;
case 1: // 原左子树更深,现在平衡
root->bf = 0;
taller = false;
break;
}
}
}
return root;
}
// 查找最大节点(用于删除操作)
AVLNode* findMaxNode(AVLNode* node) {
if (node == nullptr) return nullptr;
while (node->right != nullptr) {
node = node->right;
}
return node;
}
// 获取树的高度(用于平衡调整)
int getHeight(AVLNode* node) {
if (node == nullptr) return 0;
int leftHeight = getHeight(node->left);
int rightHeight = getHeight(node->right);
return (leftHeight > rightHeight ? leftHeight : rightHeight) + 1;
}
// AVL 树删除(含平衡调整)
AVLNode* deleteAVL(AVLNode*& root, int key, bool& shorter) {
if (root == nullptr) {
shorter = false;
return nullptr;
}
// 查找要删除的节点
if (key < root->key) {
root->left = deleteAVL(root->left, key, shorter);
if (shorter) {
// 左子树变矮,需要调整当前节点
switch (root->bf) {
case 1: // 原左重,现在平衡
root->bf = 0;
shorter = true;
break;
case 0: // 原平衡,现在右重
root->bf = -1;
shorter = false;
break;
case -1: // 原右重,现在不平衡
if (root->right->bf == 1) {
// RL型旋转
root = rightLeftRotate(root);
} else {
// RR型旋转
if (root->right->bf == 0) {
shorter = false;
} else {
shorter = true;
}
root = leftRotate(root);
}
break;
}
}
} else if (key > root->key) {
root->right = deleteAVL(root->right, key, shorter);
if (shorter) {
// 右子树变矮,需要调整当前节点
switch (root->bf) {
case -1: // 原右重,现在平衡
root->bf = 0;
shorter = true;
break;
case 0: // 原平衡,现在左重
root->bf = 1;
shorter = false;
break;
case 1: // 原左重,现在不平衡
if (root->left->bf == -1) {
// LR型旋转
root = leftRightRotate(root);
} else {
// LL型旋转
if (root->left->bf == 0) {
shorter = false;
} else {
shorter = true;
}
root = rightRotate(root);
}
break;
}
}
} else {
// 找到要删除的节点
AVLNode* temp = root;
// 情况1:叶子节点或只有一个子节点
if (root->left == nullptr) {
root = root->right;
shorter = true;
delete temp;
} else if (root->right == nullptr) {
root = root->left;
shorter = true;
delete temp;
} else {
// 情况2:有两个子节点,用左子树最大值替换
temp = findMaxNode(root->left);
root->key = temp->key;
// 删除左子树中的最大值节点
root->left = deleteAVL(root->left, temp->key, shorter);
if (shorter) {
// 左子树变矮,需要调整当前节点
switch (root->bf) {
case 1: // 原左重,现在平衡
root->bf = 0;
shorter = true;
break;
case 0: // 原平衡,现在右重
root->bf = -1;
shorter = false;
break;
case -1: // 原右重,现在不平衡
if (root->right->bf == 1) {
// RL型旋转
root = rightLeftRotate(root);
} else {
// RR型旋转
if (root->right->bf == 0) {
shorter = false;
} else {
shorter = true;
}
root = leftRotate(root);
}
break;
}
}
}
}
return root;
}
// 中序遍历(验证二叉排序树性质:升序)
void inOrderTraverse(AVLNode* root) {
if (root != nullptr) {
inOrderTraverse(root->left);
cout << root->key << " ";
inOrderTraverse(root->right);
}
}
// 销毁 AVL 树(释放内存)
void destroyAVLTree(AVLNode*& root) {
if (root != nullptr) {
destroyAVLTree(root->left);
destroyAVLTree(root->right);
delete root;
root = nullptr;
}
}
// 查找指定关键字
AVLNode* searchAVL(AVLNode* root, int key) {
if (root == nullptr || root->key == key) {
return root; // 找到或空树
}
if (key < root->key) {
return searchAVL(root->left, key); // 左子树查找
} else {
return searchAVL(root->right, key); // 右子树查找
}
}
// 获取最小值节点
AVLNode* findMinNode(AVLNode* root) {
if (root == nullptr) return nullptr;
while (root->left != nullptr) {
root = root->left;
}
return root;
}
// 检查树是否平衡
bool isBalanced(AVLNode* root) {
if (root == nullptr) return true; // 空树平衡
// 检查当前节点平衡因子对应的高度差
int leftHeight = getHeight(root->left);
int rightHeight = getHeight(root->right);
if (abs(leftHeight - rightHeight) > 1) {
return false;
}
// 递归检查左右子树
return isBalanced(root->left) && isBalanced(root->right);
}
// 计算节点总数
int countNodes(AVLNode* root) {
if (root == nullptr) return 0;
return countNodes(root->left) + countNodes(root->right) + 1;
}
// 前序遍历
void preOrderTraverse(AVLNode* root) {
if (root != nullptr) {
cout << root->key << " "; // 先访问根
preOrderTraverse(root->left);
preOrderTraverse(root->right);
}
}
// 后序遍历
void postOrderTraverse(AVLNode* root) {
if (root != nullptr) {
postOrderTraverse(root->left);
postOrderTraverse(root->right);
cout << root->key << " "; // 最后访问根
}
}
// 层次遍历
void levelOrderTraverse(AVLNode* root) {
if (root == nullptr) return;
queue<AVLNode*> q;
q.push(root);
while (!q.empty()) {
AVLNode* curr = q.front();
q.pop();
cout << curr->key << " ";
if (curr->left != nullptr) q.push(curr->left);
if (curr->right != nullptr) q.push(curr->right);
}
}
// 更新节点关键字
bool updateKey(AVLNode*& root, int oldKey, int newKey) {
bool shorter = false, taller = false;
// 先删除旧关键字
root = deleteAVL(root, oldKey, shorter);
if (!shorter) { // 若删除成功(shorter为true说明树结构变化,即存在旧关键字)
// 再插入新关键字
root = insertAVL(root, newKey, taller);
return true;
}
return false; // 旧关键字不存在,更新失败
}
// 查找前驱节点
AVLNode* findPredecessor(AVLNode* node) {
if (node == nullptr || node->left == nullptr) return nullptr;
return findMaxNode(node->left); // 左子树的最大值节点
}
// 查找后继节点
AVLNode* findSuccessor(AVLNode* node) {
if (node == nullptr || node->right == nullptr) return nullptr;
return findMinNode(node->right); // 右子树的最小值节点
}
// 复制树
AVLNode* copyTree(AVLNode* root) {
if (root == nullptr) return nullptr;
// 复制当前节点
AVLNode* newNode = new AVLNode(root->key);
newNode->bf = root->bf;
// 递归复制左右子树
newNode->left = copyTree(root->left);
newNode->right = copyTree(root->right);
return newNode;
}
(二)Python代码如下:
class AVLNode:
def __init__(self, key):
self.key = key
self.bf = 0 # 平衡因子:左子树高度 - 右子树高度
self.left = None
self.right = None
# LL 型:右旋转
def right_rotate(A):
B = A.left
A.left = B.right
B.right = A
# 更新平衡因子
A.bf = 0
B.bf = 0
return B
# RR 型:左旋转
def left_rotate(A):
B = A.right
A.right = B.left
B.left = A
# 更新平衡因子
A.bf = 0
B.bf = 0
return B
# LR 型:先左旋转(子树),再右旋转(根)
def left_right_rotate(A):
B = A.left
C = B.right
# 第一步:左旋转 B
B.right = C.left
C.left = B
# 第二步:右旋转 A
A.left = C.right
C.right = A
# 更新平衡因子
if C.bf == 1:
A.bf = -1
B.bf = 0
elif C.bf == -1:
A.bf = 0
B.bf = 1
else: # C.bf == 0
A.bf = 0
B.bf = 0
C.bf = 0
return C
# RL 型:先右旋转(子树),再左旋转(根)
def right_left_rotate(A):
B = A.right
C = B.left
# 第一步:右旋转 B
B.left = C.right
C.right = B
# 第二步:左旋转 A
A.right = C.left
C.left = A
# 更新平衡因子
if C.bf == 1:
A.bf = 0
B.bf = -1
elif C.bf == -1:
A.bf = 1
B.bf = 0
else: # C.bf == 0
A.bf = 0
B.bf = 0
C.bf = 0
return C
# AVL 树插入(含平衡调整)
def insert_avl(root, key, taller):
if root is None:
taller[0] = True
return AVLNode(key)
if key == root.key:
taller[0] = False
return root
if key < root.key:
root.left = insert_avl(root.left, key, taller)
if taller[0]:
if root.bf == 1:
if root.left.bf == 1:
root = right_rotate(root)
else:
root = left_right_rotate(root)
taller[0] = False
elif root.bf == 0:
root.bf = 1
taller[0] = True
else: # root.bf == -1
root.bf = 0
taller[0] = False
else:
root.right = insert_avl(root.right, key, taller)
if taller[0]:
if root.bf == -1:
if root.right.bf == -1:
root = left_rotate(root)
else:
root = right_left_rotate(root)
taller[0] = False
elif root.bf == 0:
root.bf = -1
taller[0] = True
else: # root.bf == 1
root.bf = 0
taller[0] = False
return root
# 查找最大节点(用于删除操作)
def find_max_node(node):
if node is None:
return None
while node.right is not None:
node = node.right
return node
# 获取树的高度(用于平衡调整)
def get_height(node):
if node is None:
return 0
left_height = get_height(node.left)
right_height = get_height(node.right)
return max(left_height, right_height) + 1
# AVL 树删除(含平衡调整)
def delete_avl(root, key, shorter):
if root is None:
shorter[0] = False
return None
if key < root.key:
root.left = delete_avl(root.left, key, shorter)
if shorter[0]:
if root.bf == 1:
root.bf = 0
shorter[0] = True
elif root.bf == 0:
root.bf = -1
shorter[0] = False
else: # root.bf == -1
if root.right.bf == 1:
root = right_left_rotate(root)
else:
if root.right.bf == 0:
shorter[0] = False
else:
shorter[0] = True
root = left_rotate(root)
elif key > root.key:
root.right = delete_avl(root.right, key, shorter)
if shorter[0]:
if root.bf == -1:
root.bf = 0
shorter[0] = True
elif root.bf == 0:
root.bf = 1
shorter[0] = False
else: # root.bf == 1
if root.left.bf == -1:
root = left_right_rotate(root)
else:
if root.left.bf == 0:
shorter[0] = False
else:
shorter[0] = True
root = right_rotate(root)
else:
temp = root
if root.left is None:
root = root.right
shorter[0] = True
elif root.right is None:
root = root.left
shorter[0] = True
else:
temp = find_max_node(root.left)
root.key = temp.key
root.left = delete_avl(root.left, temp.key, shorter)
if shorter[0]:
if root.bf == 1:
root.bf = 0
shorter[0] = True
elif root.bf == 0:
root.bf = -1
shorter[0] = False
else: # root.bf == -1
if root.right.bf == 1:
root = right_left_rotate(root)
else:
if root.right.bf == 0:
shorter[0] = False
else:
shorter[0] = True
root = left_rotate(root)
return root
# 中序遍历(验证二叉排序树性质:升序)
def in_order_traverse(root):
result = []
def traverse(node):
if node:
traverse(node.left)
result.append(node.key)
traverse(node.right)
traverse(root)
return result
# 前序遍历
def pre_order_traverse(root):
result = []
def traverse(node):
if node:
result.append(node.key)
traverse(node.left)
traverse(node.right)
traverse(root)
return result
# 后序遍历
def post_order_traverse(root):
result = []
def traverse(node):
if node:
traverse(node.left)
traverse(node.right)
result.append(node.key)
traverse(root)
return result
# 层次遍历
def level_order_traverse(root):
if root is None:
return []
result = []
queue = [root]
while queue:
curr = queue.pop(0)
result.append(curr.key)
if curr.left:
queue.append(curr.left)
if curr.right:
queue.append(curr.right)
return result
# 查找指定关键字
def search_avl(root, key):
if root is None or root.key == key:
return root
if key < root.key:
return search_avl(root.left, key)
else:
return search_avl(root.right, key)
# 获取最小值节点
def find_min_node(root):
if root is None:
return None
while root.left is not None:
root = root.left
return root
# 检查树是否平衡
def is_balanced(root):
if root is None:
return True
left_height = get_height(root.left)
right_height = get_height(root.right)
if abs(left_height - right_height) > 1:
return False
return is_balanced(root.left) and is_balanced(root.right)
# 计算节点总数
def count_nodes(root):
if root is None:
return 0
return count_nodes(root.left) + count_nodes(root.right) + 1
# 更新节点关键字
def update_key(root, old_key, new_key):
shorter = [False]
taller = [False]
# 先删除旧关键字
root = delete_avl(root, old_key, shorter)
if shorter[0] == False:
# 再插入新关键字
root = insert_avl(root, new_key, taller)
return root, True
return root, False
# 查找前驱节点
def find_predecessor(node):
if node is None or node.left is None:
return None
return find_max_node(node.left)
# 查找后继节点
def find_successor(node):
if node is None or node.right is None:
return None
return find_min_node(node.right)
# 复制树
def copy_tree(root):
if root is None:
return None
new_node = AVLNode(root.key)
new_node.bf = root.bf
new_node.left = copy_tree(root.left)
new_node.right = copy_tree(root.right)
return new_node
# 测试主函数
def main():
root = None
keys = [13, 24, 37, 90, 53]
print("插入的关键字序列:", end=" ")
for key in keys:
taller = [False]
root = insert_avl(root, key, taller)
print(key, end=" ")
print("\n")
# 遍历测试
print("插入后中序遍历(升序验证):", in_order_traverse(root))
print("插入后前序遍历:", pre_order_traverse(root))
print("插入后后序遍历:", post_order_traverse(root))
print("插入后层次遍历:", level_order_traverse(root))
print()
# 查找测试
search_key = 37
found = search_avl(root, search_key)
if found:
print(f"查找关键字 {search_key} 成功,节点值为 {found.key}")
pred = find_predecessor(found)
succ = find_successor(found)
print(f" 前驱节点:{pred.key if pred else '不存在'}")
print(f" 后继节点:{succ.key if succ else '不存在'}")
else:
print(f"查找关键字 {search_key} 失败")
print()
# 平衡性与结构信息测试
print(f"树是否平衡:{'是' if is_balanced(root) else '否'}")
print(f"树的高度:{get_height(root)}")
print(f"节点总数:{count_nodes(root)}")
print()
# 删除测试
del_key = 24
shorter = [False]
root = delete_avl(root, del_key, shorter)
print(f"删除 {del_key} 后中序遍历:", in_order_traverse(root))
print(f"删除后树是否平衡:{'是' if is_balanced(root) else '否'}")
print()
del_key = 37
shorter = [False]
root = delete_avl(root, del_key, shorter)
print(f"删除 {del_key} 后中序遍历:", in_order_traverse(root))
print(f"删除后树是否平衡:{'是' if is_balanced(root) else '否'}")
print()
# 更新关键字测试
old_key, new_key = 53, 50
root, success = update_key(root, old_key, new_key)
if success:
print(f"更新关键字 {old_key} 为 {new_key} 成功,中序遍历:", in_order_traverse(root))
else:
print(f"更新关键字 {old_key} 失败(不存在)")
print(f"更新后树是否平衡:{'是' if is_balanced(root) else '否'}")
print()
# 复制树测试
copied_root = copy_tree(root)
print("复制树的中序遍历:", in_order_traverse(copied_root))
if __name__ == "__main__":
main()
(三)Java代码如下:
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
public class AVLTree {
static class AVLNode {
int key;
int bf; // 平衡因子:左子树高度 - 右子树高度
AVLNode left;
AVLNode right;
public AVLNode(int key) {
this.key = key;
this.bf = 0;
this.left = null;
this.right = null;
}
}
// LL型:右旋转
private static AVLNode rightRotate(AVLNode A) {
AVLNode B = A.left;
A.left = B.right;
B.right = A;
// 更新平衡因子
A.bf = 0;
B.bf = 0;
return B;
}
// RR型:左旋转
private static AVLNode leftRotate(AVLNode A) {
AVLNode B = A.right;
A.right = B.left;
B.left = A;
// 更新平衡因子
A.bf = 0;
B.bf = 0;
return B;
}
// LR型:先左旋转(子树),再右旋转(根)
private static AVLNode leftRightRotate(AVLNode A) {
AVLNode B = A.left;
AVLNode C = B.right;
// 第一步:左旋转 B
B.right = C.left;
C.left = B;
// 第二步:右旋转 A
A.left = C.right;
C.right = A;
// 更新平衡因子
if (C.bf == 1) {
A.bf = -1;
B.bf = 0;
} else if (C.bf == -1) {
A.bf = 0;
B.bf = 1;
} else { // C.bf == 0
A.bf = 0;
B.bf = 0;
}
C.bf = 0;
return C;
}
// RL型:先右旋转(子树),再左旋转(根)
private static AVLNode rightLeftRotate(AVLNode A) {
AVLNode B = A.right;
AVLNode C = B.left;
// 第一步:右旋转 B
B.left = C.right;
C.right = B;
// 第二步:左旋转 A
A.right = C.left;
C.left = A;
// 更新平衡因子
if (C.bf == 1) {
A.bf = 0;
B.bf = -1;
} else if (C.bf == -1) {
A.bf = 1;
B.bf = 0;
} else { // C.bf == 0
A.bf = 0;
B.bf = 0;
}
C.bf = 0;
return C;
}
// AVL树插入(含平衡调整)
public static AVLNode insertAVL(AVLNode root, int key, boolean[] taller) {
if (root == null) {
taller[0] = true;
return new AVLNode(key);
}
if (key == root.key) {
taller[0] = false;
return root;
}
if (key < root.key) {
root.left = insertAVL(root.left, key, taller);
if (taller[0]) {
// 处理左子树插入后的平衡调整
if (root.bf == 1) {
// 需要旋转
if (root.left.bf == 1) {
root = rightRotate(root); // LL型
} else {
root = leftRightRotate(root); // LR型
}
taller[0] = false;
} else if (root.bf == 0) {
root.bf = 1;
taller[0] = true;
} else { // root.bf == -1
root.bf = 0;
taller[0] = false;
}
}
} else {
root.right = insertAVL(root.right, key, taller);
if (taller[0]) {
// 处理右子树插入后的平衡调整
if (root.bf == -1) {
// 需要旋转
if (root.right.bf == -1) {
root = leftRotate(root); // RR型
} else {
root = rightLeftRotate(root); // RL型
}
taller[0] = false;
} else if (root.bf == 0) {
root.bf = -1;
taller[0] = true;
} else { // root.bf == 1
root.bf = 0;
taller[0] = false;
}
}
}
return root;
}
// 查找最大节点
private static AVLNode findMaxNode(AVLNode node) {
if (node == null) {
return null;
}
while (node.right != null) {
node = node.right;
}
return node;
}
// 查找最小节点
private static AVLNode findMinNode(AVLNode node) {
if (node == null) {
return null;
}
while (node.left != null) {
node = node.left;
}
return node;
}
// 获取树的高度
public static int getHeight(AVLNode node) {
if (node == null) {
return 0;
}
int leftHeight = getHeight(node.left);
int rightHeight = getHeight(node.right);
return Math.max(leftHeight, rightHeight) + 1;
}
// AVL树删除(含平衡调整)
public static AVLNode deleteAVL(AVLNode root, int key, boolean[] shorter) {
if (root == null) {
shorter[0] = false;
return null;
}
if (key < root.key) {
root.left = deleteAVL(root.left, key, shorter);
if (shorter[0]) {
// 处理左子树删除后的平衡调整
if (root.bf == 1) {
root.bf = 0;
shorter[0] = true;
} else if (root.bf == 0) {
root.bf = -1;
shorter[0] = false;
} else { // root.bf == -1
if (root.right.bf == 1) {
root = rightLeftRotate(root);
} else {
if (root.right.bf == 0) {
shorter[0] = false;
} else {
shorter[0] = true;
}
root = leftRotate(root);
}
}
}
} else if (key > root.key) {
root.right = deleteAVL(root.right, key, shorter);
if (shorter[0]) {
// 处理右子树删除后的平衡调整
if (root.bf == -1) {
root.bf = 0;
shorter[0] = true;
} else if (root.bf == 0) {
root.bf = 1;
shorter[0] = false;
} else { // root.bf == 1
if (root.left.bf == -1) {
root = leftRightRotate(root);
} else {
if (root.left.bf == 0) {
shorter[0] = false;
} else {
shorter[0] = true;
}
root = rightRotate(root);
}
}
}
} else {
// 找到待删除节点
AVLNode temp = root;
if (root.left == null) {
root = root.right;
shorter[0] = true;
} else if (root.right == null) {
root = root.left;
shorter[0] = true;
} else {
// 找到左子树最大节点
temp = findMaxNode(root.left);
root.key = temp.key;
root.left = deleteAVL(root.left, temp.key, shorter);
if (shorter[0]) {
if (root.bf == 1) {
root.bf = 0;
shorter[0] = true;
} else if (root.bf == 0) {
root.bf = -1;
shorter[0] = false;
} else { // root.bf == -1
if (root.right.bf == 1) {
root = rightLeftRotate(root);
} else {
if (root.right.bf == 0) {
shorter[0] = false;
} else {
shorter[0] = true;
}
root = leftRotate(root);
}
}
}
}
}
return root;
}
// 中序遍历
public static List<Integer> inOrderTraverse(AVLNode root) {
List<Integer> result = new ArrayList<>();
inOrderHelper(root, result);
return result;
}
private static void inOrderHelper(AVLNode node, List<Integer> result) {
if (node != null) {
inOrderHelper(node.left, result);
result.add(node.key);
inOrderHelper(node.right, result);
}
}
// 前序遍历
public static List<Integer> preOrderTraverse(AVLNode root) {
List<Integer> result = new ArrayList<>();
preOrderHelper(root, result);
return result;
}
private static void preOrderHelper(AVLNode node, List<Integer> result) {
if (node != null) {
result.add(node.key);
preOrderHelper(node.left, result);
preOrderHelper(node.right, result);
}
}
// 后序遍历
public static List<Integer> postOrderTraverse(AVLNode root) {
List<Integer> result = new ArrayList<>();
postOrderHelper(root, result);
return result;
}
private static void postOrderHelper(AVLNode node, List<Integer> result) {
if (node != null) {
postOrderHelper(node.left, result);
postOrderHelper(node.right, result);
result.add(node.key);
}
}
// 层次遍历
public static List<Integer> levelOrderTraverse(AVLNode root) {
if (root == null) {
return new ArrayList<>();
}
List<Integer> result = new ArrayList<>();
Queue<AVLNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
AVLNode curr = queue.poll();
result.add(curr.key);
if (curr.left != null) {
queue.offer(curr.left);
}
if (curr.right != null) {
queue.offer(curr.right);
}
}
return result;
}
// 查找指定关键字
public static AVLNode searchAVL(AVLNode root, int key) {
if (root == null || root.key == key) {
return root;
}
if (key < root.key) {
return searchAVL(root.left, key);
} else {
return searchAVL(root.right, key);
}
}
// 检查树是否平衡
public static boolean isBalanced(AVLNode root) {
if (root == null) {
return true;
}
int leftHeight = getHeight(root.left);
int rightHeight = getHeight(root.right);
if (Math.abs(leftHeight - rightHeight) > 1) {
return false;
}
return isBalanced(root.left) && isBalanced(root.right);
}
// 计算节点总数
public static int countNodes(AVLNode root) {
if (root == null) {
return 0;
}
return countNodes(root.left) + countNodes(root.right) + 1;
}
// 查找前驱节点
public static AVLNode findPredecessor(AVLNode node) {
if (node == null || node.left == null) {
return null;
}
return findMaxNode(node.left);
}
// 查找后继节点
public static AVLNode findSuccessor(AVLNode node) {
if (node == null || node.right == null) {
return null;
}
return findMinNode(node.right);
}
// 更新节点关键字
public static AVLNode updateKey(AVLNode root, int oldKey, int newKey, boolean[] success) {
boolean[] shorter = new boolean[1];
boolean[] taller = new boolean[1];
// 先删除旧关键字
root = deleteAVL(root, oldKey, shorter);
// 如果删除失败(旧关键字不存在),shorter[0]会是false
if (!shorter[0]) {
// 插入新关键字
root = insertAVL(root, newKey, taller);
success[0] = true;
} else {
success[0] = false;
}
return root;
}
// 复制树
public static AVLNode copyTree(AVLNode root) {
if (root == null) {
return null;
}
AVLNode newNode = new AVLNode(root.key);
newNode.bf = root.bf;
newNode.left = copyTree(root.left);
newNode.right = copyTree(root.right);
return newNode;
}
// 测试主函数
public static void main(String[] args) {
AVLNode root = null;
int[] keys = {13, 24, 37, 90, 53};
System.out.print("插入的关键字序列: ");
for (int key : keys) {
boolean[] taller = new boolean[1];
root = insertAVL(root, key, taller);
System.out.print(key + " ");
}
System.out.println("\n");
// 遍历测试
System.out.println("插入后中序遍历(升序验证): " + inOrderTraverse(root));
System.out.println("插入后前序遍历: " + preOrderTraverse(root));
System.out.println("插入后后序遍历: " + postOrderTraverse(root));
System.out.println("插入后层次遍历: " + levelOrderTraverse(root));
System.out.println();
// 查找测试
int searchKey = 37;
AVLNode found = searchAVL(root, searchKey);
if (found != null) {
System.out.println("查找关键字 " + searchKey + " 成功,节点值为 " + found.key);
AVLNode pred = findPredecessor(found);
AVLNode succ = findSuccessor(found);
System.out.println(" 前驱节点:" + (pred != null ? pred.key : "不存在"));
System.out.println(" 后继节点:" + (succ != null ? succ.key : "不存在"));
} else {
System.out.println("查找关键字 " + searchKey + " 失败");
}
System.out.println();
// 平衡性与结构信息测试
System.out.println("树是否平衡:" + (isBalanced(root) ? "是" : "否"));
System.out.println("树的高度:" + getHeight(root));
System.out.println("节点总数:" + countNodes(root));
System.out.println();
// 删除测试
int delKey = 24;
boolean[] shorter = new boolean[1];
root = deleteAVL(root, delKey, shorter);
System.out.println("删除 " + delKey + " 后中序遍历: " + inOrderTraverse(root));
System.out.println("删除后树是否平衡:" + (isBalanced(root) ? "是" : "否"));
System.out.println();
delKey = 37;
shorter = new boolean[1];
root = deleteAVL(root, delKey, shorter);
System.out.println("删除 " + delKey + " 后中序遍历: " + inOrderTraverse(root));
System.out.println("删除后树是否平衡:" + (isBalanced(root) ? "是" : "否"));
System.out.println();
// 更新关键字测试
int oldKey = 53, newKey = 50;
boolean[] success = new boolean[1];
root = updateKey(root, oldKey, newKey, success);
if (success[0]) {
System.out.println("更新关键字 " + oldKey + " 为 " + newKey + " 成功,中序遍历: " + inOrderTraverse(root));
} else {
System.out.println("更新关键字 " + oldKey + " 失败(不存在)");
}
System.out.println("更新后树是否平衡:" + (isBalanced(root) ? "是" : "否"));
System.out.println();
// 复制树测试
AVLNode copiedRoot = copyTree(root);
System.out.println("复制树的中序遍历: " + inOrderTraverse(copiedRoot));
}
}
十四、程序运行结果完整展示
(一)C++运行结果

(二)Python运行结果

(三)Java运行结果

十五、总结与展望
AVL 树作为最早实现自平衡的二叉搜索树,通过平衡因子(左子树高度与右子树高度之差)监控节点平衡状态,并借助旋转操作(LL、RR、LR、RL)动态调整树结构,从根本上解决了普通二叉搜索树在极端情况下退化为线性结构的问题,确保了插入、删除、查找等操作的时间复杂度稳定在 O(log n),奠定了其在数据结构中平衡树的经典地位。本章将系统总结其技术要点,并展望未来优化方向与学习价值。
(一)技术要点回顾
AVL 树的核心竞争力源于其严谨的自平衡机制:在理论基础层面,基于二叉搜索树的基本特性,通过严格控制平衡因子在 [-1, 0, 1] 范围内实现结构平衡;在核心操作层面,通过旋转操作修复失衡节点,配合插入/删除后的回溯调整机制,实现全生命周期的动态平衡维护;在辅助功能层面,结合中序遍历、高效查找及性能监控(如平衡因子分布统计、旋转次数计数),形成完整的功能体系。这种设计既保留了二叉搜索树的高效查询特性,又通过自平衡机制消除了性能波动风险。
(二)未来优化方向
尽管 AVL 树已成为平衡树的经典实现,但其仍存在优化空间。未来可重点探索三方面:一是减少旋转操作次数,通过预判失衡趋势或引入自适应平衡阈值,降低调整开销;二是与其他平衡树机制融合,例如借鉴红黑树的“局部调整”思想降低旋转频率,或结合 B 树的多路平衡特性适应磁盘存储场景;三是面向并行计算环境设计无锁化节点更新协议,提升多线程场景下的操作效率。
学习价值核心:AVL 树的设计思想为理解复杂数据结构提供了范式级参考。其递归实现的节点操作、基于状态标志(平衡因子)的决策逻辑、以及动态平衡维护的全局视角,贯穿于 B+ 树、跳表、线段树等高级数据结构的实现中,是培养算法思维与系统设计能力的关键跳板。
从技术演进视角看,AVL 树作为平衡树领域的开创性成果,不仅为后续红黑树、Splay 树等变体提供了理论基础,更深刻揭示了“通过状态量化与局部调整实现全局优化”的计算机科学思想。掌握其原理与实现,对构建高效、稳定的系统组件具有重要实践意义。
更多推荐

所有评论(0)