目录
前言
A.建议
1.学习算法最重要的是理解算法的每一步,而不是记住算法。
2.建议读者学习算法的时候,自己手动一步一步地运行算法。
B.简介
在C语言中实现平衡二叉搜索树(AVL树)的调整,通常涉及四种旋转操作来恢复树的平衡状态。插入或删除节点后,如果某个节点的平衡因子(BF = 左子树高度 - 右子树高度)绝对值大于1,则需要进行相应的旋转。
一 代码实现
以下均为简化伪代码
右旋(Right Rotation, RR): 当一个节点的左子节点的平衡因子为+2时(即左子节点的左子树比其右子树高),需要对这个节点执行右旋操作以恢复平衡。右旋是围绕着当前节点(X)和它的左孩子(Y)进行的。
// 以下是一个简化版的RR旋转操作伪代码
void rightRotate(AVLNode** root, AVLNode* X) {
// Y 是 X 的左孩子
AVLNode* Y = X->left;
// Z 是 Y 的右孩子
X->left = Y->right;
if (Y->right != NULL)
Y->right->parent = X;
// Y 成为新的父节点
Y->right = X;
X->parent = Y;
// 更新节点的高度并检查新根节点的平衡性
updateHeight(X);
updateHeight(Y);
// 如果原节点是根节点,则更新根节点
if (*root == X)
*root = Y;
// 检查是否需要进一步旋转(可能是LL)
checkBalance(root, Y);
}
// updateHeight函数用于递归更新节点及其祖先节点的高度
void updateHeight(AVLNode* node) {
int hl = height(node->left);
int hr = height(node->right);
node->height = max(hl, hr) + 1;
}
// height函数计算给定节点的高度
int height(AVLNode* node) {
return node ? node->height : 0;
}
左旋(Left Rotation, LL): 当一个节点的右子节点的平衡因子为-2时(即右子节点的右子树比其左子树低两层),需要对这个节点执行左旋操作。左旋是围绕着当前节点(Y)和它的右孩子(X)进行的。
// 左旋函数,处理节点Y和其右孩子X
void leftRotate(AVLNode** root, AVLNode* Y) {
// X 是 Y 的右孩子
AVLNode* X = Y->right;
// T 是 X 的左孩子
Y->right = X->left;
if (X->left != NULL)
X->left->parent = Y;
// X 成为新的父节点,其左孩子变为原父节点Y
X->left = Y;
Y->parent = X;
// 更新节点的高度并检查新根节点的平衡性
updateHeight(Y);
updateHeight(X);
// 如果原节点是根节点,则更新根节点
if (*root == Y)
*root = X;
// 检查是否需要进一步旋转(可能是RR)
checkBalance(root, X);
}
// 递归更新节点及其祖先节点的高度
void updateHeight(AVLNode* node) {
int hl = height(node->left);
int hr = height(node->right);
node->height = max(hl, hr) + 1;
}
// 计算给定节点的高度
int height(AVLNode* node) {
return node ? node->height : 0;
}
左右旋(Left-Right Rotation, LR): 当一个节点的左孩子具有正平衡因子,且该左孩子的右孩子具有负平衡因子时,需要先对左孩子执行右旋,再对原始节点执行左旋。
// 先对左孩子执行右旋,再对原始节点执行左旋
void leftRightRotate(AVLNode** root, AVLNode* Z) {
// Y 是 Z 的左孩子
AVLNode* Y = Z->left;
// X 是 Y 的右孩子
rightRotate(root, Y); // 先进行右旋
// 现在Z的左孩子是X,X的右孩子是Y
leftRotate(root, Z); // 再进行左旋
}
// 右旋函数
void rightRotate(AVLNode** root, AVLNode* Y) {
// X 是 Y 的左孩子
// ... (此处为右旋操作的实现,参考上面的回答)
}
// 左旋函数
void leftRotate(AVLNode** root, AVLNode* Z) {
// Y 是 Z 的右孩子
// ... (此处为左旋操作的实现,参考上面的回答)
}
// 更新高度和检查平衡性的辅助函数保持不变
void updateHeight(AVLNode* node);
int height(AVLNode* node);
// 检查并修复不平衡节点的辅助函数
void checkBalance(AVLNode** root, AVLNode* node);
右左旋(Right-Left Rotation, RL): 当一个节点的右孩子具有负平衡因子,且该右孩子的左孩子具有正平衡因子时,需要先对右孩子执行左旋,再对原始节点执行右旋。
// 先对右孩子执行左旋,再对原始节点执行右旋
void rightLeftRotate(AVLNode** root, AVLNode* Z) {
// Y 是 Z 的右孩子
AVLNode* Y = Z->right;
// X 是 Y 的左孩子
leftRotate(root, Y); // 先进行左旋
// 现在Z的右孩子是X,X的左孩子是Y
rightRotate(root, Z); // 再进行右旋
}
// 左旋函数
void leftRotate(AVLNode** root, AVLNode* Y) {
// X 是 Y 的右孩子
// ... (此处为左旋操作的实现,参考上面的回答)
}
// 右旋函数
void rightRotate(AVLNode** root, AVLNode* Z) {
// Y 是 Z 的左孩子
// ... (此处为右旋操作的实现,参考上面的回答)
}
// 更新高度和检查平衡性的辅助函数保持不变
void updateHeight(AVLNode* node);
int height(AVLNode* node);
// 检查并修复不平衡节点的辅助函数
void checkBalance(AVLNode** root, AVLNode* node);
二 时空复杂度
A.时间复杂度:
-
最坏情况下的旋转次数:在最坏情况下,从插入或删除操作开始的节点到根节点路径上的所有节点可能都需要进行旋转调整。由于AVL树的高度始终保持在
范围内(其中n为节点数量),因此需要调整的最大深度不会超过
。
-
总体调整时间复杂度:每次旋转的时间复杂度为O(1),故最多需要
次旋转,所以整体调整过程的时间复杂度为
。
B.空间复杂度:
- 辅助空间复杂度:在执行旋转调整过程中,仅需临时存储几个指向父节点和子节点的指针,并不需要额外的空间来存储数据结构本身,因此辅助空间复杂度为O(1)。
三 优缺点
A.优点:
-
高效查询性能:由于平衡二叉搜索树确保了左右子树高度差不超过1,因此可以保证在最坏情况下查找、插入和删除操作的时间复杂度均为O(log n),相对于非平衡二叉搜索树有稳定的高性能。
-
稳定性:通过对树进行自动调整,避免了因数据分布不均导致的退化为链表状结构,使得所有操作的性能趋于稳定。
-
自适应性:能够根据数据的变化动态调整树的结构,以维持较好的查询效率。
B.缺点:
-
额外开销:每次插入或删除操作后都需要检查并可能执行一系列的旋转操作来重新平衡树,这增加了额外的操作次数和复杂度,与简单二叉搜索树相比,在插入和删除操作上可能会稍微慢一些。
-
实现复杂性:平衡二叉树的实现相对复杂,需要处理四种不同的旋转情况,并且要维护每个节点的高度信息以及递归地向上更新这些信息。
-
空间消耗:虽然单次旋转的空间复杂度是O(1),但为了存储平衡因子等额外信息,每个节点需要更多的存储空间。
-
实际场景中的权衡:对于某些特定应用场景,尤其是那些频繁插入和删除但在插入或删除后很少进行查询的数据集,维持树的严格平衡可能不如采用其他自平衡树(如红黑树)更为合适,因为红黑树在插入和删除操作上的平均时间复杂度也是O(log n),但在实现上略为简化,牺牲了一点点查找性能换取更简单的旋转规则。
四 现实中的应用
-
数据库索引:在关系型数据库管理系统中,平衡二叉搜索树被用于实现B树或B+树这样的索引结构,确保快速进行记录的查找、插入和删除操作。例如,MySQL InnoDB存储引擎中的聚簇索引就是基于B+树实现的。
-
文件系统:现代文件系统的目录结构也经常采用自平衡二叉搜索树来组织,这样可以迅速定位到特定的文件或子目录,提高文件的读写效率。
-
内存管理:在操作系统内核的内存分配器中,使用平衡二叉树来维护空闲内存块的列表,使得内存分配和回收操作能以接近O(log n)的时间复杂度完成,提高了内存管理效率。
-
缓存系统:在缓存系统设计中,比如LRU(最近最少使用算法)缓存淘汰策略的实现中,可以借助平衡二叉树来存储键值对并根据访问频率重新排序,使得高频使用的元素总能保持在靠近根节点的位置,便于快速查找和更新。
-
实时数据分析与处理:在大数据分析、流式计算等领域,平衡二叉搜索树用于建立动态的数据结构,支持高效的实时查询和聚合操作。
-
地图服务:在线地图服务为了提供快速的地理空间索引,也会利用平衡二叉搜索树变种(如R树)来存储空间对象,并实现在不同尺度上的快速检索。