实现自平衡二叉搜索树(AVL树)的C++指南
在这篇博客中,我们将深入探讨自平衡二叉搜索树,特别是AVL树(由Adelson-Velsky和Landis命名),以及如何使用C++实现它。AVL树是一种特别的二叉搜索树,它可以在插入和删除节点后自动保持平衡,确保任何操作的时间复杂度始终为O(log n)。
阅读前我希望你能花点时间看看的问题以及回答
1. 什么是AVL树?
答: AVL树是一种自平衡二叉搜索树。在这种树中,任何节点的两个子树的高度最多相差1。这种高度平衡保证了树的操作(如搜索、插入、删除)在最坏情况下都能保持对数时间复杂度。
2. 为啥AVL树会被叫做AVL树?
答: AVL树得名于其发明者G. M. Adelson-Velsky和Evgenii Landis,他们在1962年首次提出了这种数据结构。
3. 为什么AVL树要保持平衡?
答: AVL树保持平衡主要是为了避免二叉搜索树退化成链表的情况,这种情况下,大多数树操作的时间复杂度会从O(log n)恶化为O(n)。通过保持树的平衡,AVL树确保了最坏情况下的操作效率。
4. AVL树的平衡因子是什么?
答: 平衡因子是节点的左子树高度与右子树高度的差。平衡因子的可能值为-1,0,或1。如果任何节点的平衡因子超出这个范围,AVL树会通过旋转来重新平衡。
5. AVL树中的旋转是什么?
答: 旋转是AVL树重平衡过程中的基本操作,包括左旋和右旋。旋转帮助树恢复平衡因子的正常范围(-1, 0, 1),从而维持操作的效率。
6. 插入新节点后,AVL树如何自我调整?
答: 在AVL树中插入新节点后,可能会导致某些节点的平衡因子超出允许范围。此时,树会从插入点向上遍历至根节点,找到第一个平衡因子绝对值超过1的节点,然后根据子树的状态执行相应的旋转操作。
7. 删除节点后,AVL树如何维持平衡?
答: 当在AVL树中删除节点后,如果任何节点的平衡因子变得不在允许的范围内,AVL树会执行旋转操作来重新平衡。删除操作可能需要从删除点向上追溯到根节点,进行多次旋转以确保整个树的平衡。
8. AVL树的旋转操作具体包括哪些类型?
答: AVL树主要包括四种旋转操作:左旋、右旋、左-右旋和右-左旋。左旋和右旋是单一方向的基本旋转,而左-右旋和右-左旋是两步操作,先对子节点进行旋转,然后对当前节点进行旋转。
9. AVL树的搜索效率如何?
答: 由于AVL树是平衡的,其搜索效率总是保持在O(log n),其中n是树中节点的数量。这确保了即使在连续的插入和删除操作之后,搜索操作也能快速完成。
10. AVL树与其他类型的树(如红黑树)相比有何优势和劣势?
答: AVL树的主要优势在于它提供了更严格的平衡,因此对于那些读操作比写操作频繁的场景更加适用,可以提供更快的查找性能。不过,这种严格的平衡也意味着插入和删除操作可能需要更频繁的旋转,导致写操作比红黑树稍慢。红黑树提供了更多的灵活性,牺牲了一点平衡(允许树的高度是最小高度的两倍),以减少在更新操作中需要的旋转次数。
带着上面的问题,下面我们一起来看看关于AVL树的详解以及对应的C++代码的详细实现。
什么是AVL树?
AVL树是一种自平衡二叉搜索树。对于树中的每一个节点,其左子树和右子树的高度最多相差1。这种高度平衡保证了树的深度保持对数级别,从而使得搜索、插入和删除操作都非常高效。
AVL树的特性
- 高度平衡:对于任何节点,其左右子树的高度差(平衡因子)必须是-1、0或1。
- 旋转操作:通过左旋、右旋、左右旋和右左旋等操作来维护树的平衡。
- 动态更新:在每次插入或删除节点后,AVL树会通过旋转来重新平衡自己。
C++实现
接下来,我们将展示如何用C++实现一个基本的AVL树,包括节点定义、旋转操作和插入功能。
节点定义
每个节点存储一个键值、高度信息以及指向其左子节点和右子节点的指针。
struct AVLNode {
int key;
int height;
AVLNode *left;
AVLNode *right;
AVLNode(int k) : key(k), height(1), left(nullptr), right(nullptr) {}
};
辅助函数
我们需要一些辅助函数来获取节点的高度和更新节点的高度,以及计算节点的平衡因子。
int getHeight(AVLNode* N) {
if (N == nullptr)
return 0;
return N->height;
}
int getBalanceFactor(AVLNode* N) {
if (N == nullptr)
return 0;
return getHeight(N->left) - getHeight(N->right);
}
void updateHeight(AVLNode* N) {
N->height = 1 + max(getHeight(N->left), getHeight(N->right));
}
旋转操作
实现右旋和左旋操作。
AVLNode* rightRotate(AVLNode* y) {
AVLNode* x = y->left;
AVLNode* T2 = x->right;
// Perform rotation
x->right = y;
y->left = T2;
// Update heights
updateHeight(y);
updateHeight(x);
return x; // new root
}
AVLNode* leftRotate(AVLNode* x) {
AVLNode* y = x->right;
AVLNode* T2 = y->left;
// Perform rotation
y->left = x;
x->right = T2;
// Update heights
updateHeight(x);
updateHeight(y);
return y; // new root
}
插入操作
插入新键并通过旋转操作保持平衡。
AVLNode* insert(AVLNode* node, int key) {
// 1. Perform the normal BST insertion
if (node == nullptr)
return new AVLNode(key);
if (key < node->key)
node->left = insert(node->left, key);
else if (key > node->key)
node->right = insert(node->right, key);
else
return node;
// 2. Update height of this ancestor node
updateHeight(node);
// 3. Get the balance factor
int balance = getBalanceFactor(node);
// 4. If unbalanced, then there are 4 cases
// Left Left Case
if (balance > 1 && key < node->left->key)
return rightRotate(node);
// RightRight Case
if (balance < -1 && key > node->right->key)
return leftRotate(node);
// Left Right Case
if (balance > 1 && key > node->left->key) {
node->left = leftRotate(node->left);
return rightRotate(node);
}
// Right Left Case
if (balance < -1 && key < node->right->key) {
node->right = rightRotate(node->right);
return leftRotate(node);
}
// return the (unchanged) node pointer
return node;
}
AVL树的删除操作和其他功能
在AVL树中,删除操作是相对复杂的部分,因为在删除节点后,树可能会失去平衡。我们需要在删除节点后通过旋转操作重新平衡树。除了删除操作,我们还将实现树的遍历和搜索功能。
删除操作
删除节点需要处理三种情况:删除的节点是叶子节点,只有一个孩子的节点,或有两个孩子的节点。对于有两个孩子的节点,我们通常找到其后继节点(右子树中的最小节点)来替代它,然后删除原节点。
AVLNode* minValueNode(AVLNode* node) {
AVLNode* current = node;
while (current->left != nullptr)
current = current->left;
return current;
}
AVLNode* deleteNode(AVLNode* root, int key) {
// STEP 1: Perform standard BST delete
if (root == nullptr)
return root;
if (key < root->key)
root->left = deleteNode(root->left, key);
else if (key > root->key)
root->right = deleteNode(root->right, key);
else {
if ((root->left == nullptr) || (root->right == nullptr)) {
AVLNode* temp = root->left ? root->left : root->right;
if (temp == nullptr) {
temp = root;
root = nullptr;
} else {
*root = *temp;
}
delete temp;
} else {
AVLNode* temp = minValueNode(root->right);
root->key = temp->key;
root->right = deleteNode(root->right, temp->key);
}
}
if (root == nullptr)
return root;
// STEP 2: Update the height of the current node
updateHeight(root);
// STEP 3: Get the balance factor
int balance = getBalanceFactor(root);
// STEP 4: Balance the tree
if (balance > 1 && getBalanceFactor(root->left) >= 0)
return rightRotate(root);
if (balance > 1 && getBalanceFactor(root->left) < 0) {
root->left = leftRotate(root->left);
return rightRotate(root);
}
if (balance < -1 && getBalanceFactor(root->right) <= 0)
return leftRotate(root);
if (balance < -1 && getBalanceFactor(root->right) > 0) {
root->right = rightRotate(root->right);
return leftRotate(root);
}
return root;
}
其他功能:遍历和搜索
遍历树通常有三种方式:前序、中序和后序。这里我们实现中序遍历,因为它可以按排序顺序输出树中的元素。
void inOrder(AVLNode* root) {
if (root != nullptr) {
inOrder(root->left);
std::cout << root->key << " ";
inOrder(root->right);
}
}
搜索功能:
AVLNode* search(AVLNode* root, int key) {
if (root == nullptr || root->key == key)
return root;
if (root->key < key)
return search(root->right, key);
return search(root->left, key);
}
完整的C++代码示例
现在让我们把前面提到的这些部分组合成一个完整的C++程序。
#include <iostream>
#include <algorithm>
// AVL树节点结构体
struct AVLNode {
int key; // 节点键值
int height; // 节点高度
AVLNode *left; // 左子节点
AVLNode *right; // 右子节点
// 构造函数,初始化键值、高度和子节点
AVLNode(int k) : key(k), height(1), left(nullptr), right(nullptr) {}
};
// 获取节点的高度
int getHeight(AVLNode* N) {
if (N == nullptr)
return 0;
return N->height;
}
// 计算节点的平衡因子
int getBalanceFactor(AVLNode* N) {
if (N == nullptr)
return 0;
return getHeight(N->left) - getHeight(N->right);
}
// 更新节点的高度
void updateHeight(AVLNode* N) {
if (N != nullptr)
N->height = 1 + std::max(getHeight(N->left), getHeight(N->right));
}
// 右旋转操作
AVLNode* rightRotate(AVLNode* y) {
AVLNode* x = y->left;
AVLNode* T2 = x->right;
x->right =y;
y->left = T2;
updateHeight(y);
updateHeight(x);
return x;
}
// 左旋转操作
AVLNode* leftRotate(AVLNode* x) {
AVLNode* y = x->right;
AVLNode* T2 = y->left;
y->left = x;
x->right = T2;
updateHeight(x);
updateHeight(y);
return y;
}
// 插入节点
AVLNode* insert(AVLNode* node, int key) {
// 如果是空节点,创建新节点
if (node == nullptr)
return new AVLNode(key);
// 根据键值递归插入左子树或右子树
if (key < node->key)
node->left = insert(node->left, key);
else if (key > node->key)
node->right = insert(node->right, key);
else
return node;
// 更新节点高度
updateHeight(node);
// 获取节点的平衡因子
int balance = getBalanceFactor(node);
// 根据平衡因子进行旋转调整
if (balance > 1 && key < node->left->key)
return rightRotate(node);
if (balance < -1 && key > node->right->key)
return leftRotate(node);
if (balance > 1 && key > node->left->key) {
node->left = leftRotate(node->left);
return rightRotate(node);
}
if (balance < -1 && key < node->right->key) {
node->right = rightRotate(node->right);
return leftRotate(node);
}
return node;
}
// 中序遍历
void inOrder(AVLNode* root) {
if (root != nullptr) {
inOrder(root->left);
std::cout << root->key << " ";
inOrder(root->right);
}
}
// 找到给定节点的最小值节点
AVLNode* minValueNode(AVLNode* node) {
AVLNode* current = node;
while (current->left != nullptr)
current = current->left;
return current;
}
// 删除节点
AVLNode* deleteNode(AVLNode* root, int key) {
// 如果树为空,返回原树
if (root == nullptr)
return root;
// 递归地在左子树或右子树中删除节点
if (key < root->key)
root->left = deleteNode(root->left, key);
else if (key > root->key)
root->right = deleteNode(root->right, key);
else {
// 找到要删除的节点
if ((root->left == nullptr) || (root->right == nullptr)) {
AVLNode* temp = root->left ? root->left : root->right;
if (temp == nullptr) {
temp = root;
root = nullptr;
} else {
*root = *temp;
}
delete temp;
} else {
AVLNode* temp = minValueNode(root->right);
root->key = temp->key;
root->right = deleteNode(root->right, temp->key);
}
}
// 如果树为空,返回空树
if (root == nullptr)
return root;
// 更新节点高度
updateHeight(root);
// 获取节点的平衡因子
int balance = getBalanceFactor(root);
// 根据平衡因子进行旋转调整
if (balance > 1 && getBalanceFactor(root->left) >= 0)
return rightRotate(root);
if (balance > 1 && getBalanceFactor(root->left) < 0) {
root->left = leftRotate(root->left);
return rightRotate(root);
}
if (balance < -1 && getBalanceFactor(root->right) <= 0)
return leftRotate(root);
if (balance < -1 && getBalanceFactor(root->right) > 0) {
root->right = rightRotate(root->right);
return leftRotate(root);
}
return root;
}
int main() {
AVLNode* root = nullptr;
// 插入节点
root = insert(root, 10);
root = insert(root, 20);
root = insert(root, 30);
root = insert(root, 40);
root = insert(root, 50);
root = insert(root, 25);
// 中序遍历 AVL 树
std::cout << "Inorder traversal of the constructed AVL tree is \n";
inOrder(root);
std::cout << std::endl;
// 删除节点 20
root = deleteNode(root, 20);
// 再次中序遍历 AVL 树
std::cout << "Inorder traversal after deletion of 20 \n";
inOrder(root);
std::cout << std::endl;
return 0;
}
在这个demo中,我们首先创建了一个AVL树并插入了几个键值,然后执行了一个删除操作,并且显示了删除前后的中序遍历的结果。该demo主要演示如何在保持AVL树平衡的同时添加和删除节点,对于一些其它更加复杂的操作大家可以在理解了AVL树的基本原理的基础上进行添加。记住一句话,万变不离其宗,基础决定伟大。
Conclusion
AVL树通过在每次插入和删除操作后维持高度平衡,确保了操作的高效性。使用C++自己实现AVL树不仅可以加深我们对动态数据结构的理解,还能切实提高解决实际问题的能力。虽然标准库中已提供了如 std::set
、std::map
、std::multiset
和 std::multimap
等基于红黑树的平衡搜索树实现,但深入理解和实现AVL树对于学习和掌握高级数据结构还是对我们非常有帮助。
希望这篇博客能帮助你了解和实现自己的AVL树。练习实现这种数据结构是一个非常好的学习过程,能够帮助理解树结构的动态平衡机制。