1. AVL树的基本概念
想象一下你正在整理书架上的书籍。如果按照字母顺序随意摆放,找书时可能需要从第一本开始逐个查找,效率极低。这就是普通二叉搜索树可能退化成链表的情况。AVL树就像一个有自动整理功能的智能书架,始终保持左右两侧高度平衡,确保查找效率稳定在O(logN)水平。
AVL树由两位苏联数学家Adelson-Velsky和Landis在1962年提出,是最早的自平衡二叉搜索树。它的核心特性是:任何节点的左右子树高度差(平衡因子)绝对值不超过1。这种严格平衡带来的代价是插入/删除时需要额外维护平衡,但换来了稳定的查询性能。
平衡因子计算公式:某节点右子树高度 - 左子树高度。正常范围在[-1,0,1]之间
2. 平衡因子的奥秘
平衡因子是AVL树实现自平衡的关键指标。我刚开始学习时经常混淆高度和平衡因子的计算顺序。后来发现一个记忆诀窍:把树想象成弹簧秤,平衡因子就是秤的指针。当左侧太重(平衡因子<-1)或右侧太重(平衡因子>1)时,就需要通过旋转操作来调平。
计算高度的递归实现如下(注意空节点高度为0):
int height(Node* node) { return node ? node->height : 0; }更新高度和平衡因子的典型操作:
void updateHeight(Node* node) { node->height = 1 + max(height(node->left), height(node->right)); node->balance = height(node->right) - height(node->left); }3. 四种旋转操作详解
3.1 右旋(LL型失衡)
当连续出现两个左偏节点时(平衡因子<-1),就像书架左侧堆了太多书。这时需要将中间节点上提:
Node* rightRotate(Node* y) { Node* x = y->left; Node* T2 = x->right; x->right = y; y->left = T2; updateHeight(y); updateHeight(x); return x; // 返回新的根节点 }3.2 左旋(RR型失衡)
与右旋对称,处理连续右偏的情况:
Node* leftRotate(Node* x) { Node* y = x->right; Node* T2 = y->left; y->left = x; x->right = T2; updateHeight(x); updateHeight(y); return y; }3.3 左右双旋(LR型失衡)
当出现"左-右"折线型失衡时,需要先对左子树左旋变成LL型,再整体右旋:
Node* leftRightRotate(Node* z) { z->left = leftRotate(z->left); return rightRotate(z); }3.4 右左双旋(RL型失衡)
与LR型对称的"右-左"折线型失衡处理:
Node* rightLeftRotate(Node* z) { z->right = rightRotate(z->right); return leftRotate(z); }4. 插入操作的平衡维护
插入新节点后,需要从插入点向上回溯检查平衡。我在实际编码时经常忘记更新父节点指针,导致树结构断裂。关键是要注意三步:
- 标准BST插入
- 更新高度和平衡因子
- 检查并修复失衡
Node* insert(Node* node, int key) { // 1. 标准BST插入 if (!node) return newNode(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. 更新高度 updateHeight(node); // 3. 检查平衡 int balance = node->balance; // LL型 if (balance < -1 && key < node->left->key) return rightRotate(node); // RR型 if (balance > 1 && key > node->right->key) return leftRotate(node); // LR型 if (balance < -1 && key > node->left->key) return leftRightRotate(node); // RL型 if (balance > 1 && key < node->right->key) return rightLeftRotate(node); return node; }5. 删除操作的平衡维护
删除比插入更复杂,因为可能需要在多个位置进行平衡调整。我的经验是特别注意处理同时有左右子节点的情况:
Node* deleteNode(Node* root, int key) { // 标准BST删除 if (!root) 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 || !root->right) { Node* temp = root->left ? root->left : root->right; if (!temp) { temp = root; root = nullptr; } else *root = *temp; delete temp; } else { // 双子节点:用后继节点替换 Node* temp = minValueNode(root->right); root->key = temp->key; root->right = deleteNode(root->right, temp->key); } } if (!root) return root; // 更新高度 updateHeight(root); // 平衡检查(与插入类似) int balance = root->balance; // LL型 if (balance < -1 && root->left->balance <= 0) return rightRotate(root); // LR型 if (balance < -1 && root->left->balance > 0) return leftRightRotate(root); // RR型 if (balance > 1 && root->right->balance >= 0) return leftRotate(root); // RL型 if (balance > 1 && root->right->balance < 0) return rightLeftRotate(root); return root; }6. 完整C++实现
下面是一个经过实际测试的AVL树实现,包含常用接口:
#include <algorithm> #include <iostream> template <typename K, typename V> class AVLTree { private: struct Node { K key; V value; int height; Node *left, *right; Node(K k, V v) : key(k), value(v), height(1), left(nullptr), right(nullptr) {} }; Node* root; // 辅助函数 int height(Node* n) { return n ? n->height : 0; } int balanceFactor(Node* n) { return n ? height(n->right) - height(n->left) : 0; } void updateHeight(Node* n) { n->height = 1 + std::max(height(n->left), height(n->right)); } // 旋转操作 Node* rotateRight(Node* y) { Node* x = y->left; y->left = x->right; x->right = y; updateHeight(y); updateHeight(x); return x; } Node* rotateLeft(Node* x) { Node* y = x->right; x->right = y->left; y->left = x; updateHeight(x); updateHeight(y); return y; } Node* rebalance(Node* node) { updateHeight(node); int bf = balanceFactor(node); if (bf > 1) { if (balanceFactor(node->right) < 0) node->right = rotateRight(node->right); return rotateLeft(node); } if (bf < -1) { if (balanceFactor(node->left) > 0) node->left = rotateLeft(node->left); return rotateRight(node); } return node; } Node* insert(Node* node, K key, V value) { if (!node) return new Node(key, value); if (key < node->key) node->left = insert(node->left, key, value); else if (key > node->key) node->right = insert(node->right, key, value); else node->value = value; return rebalance(node); } Node* findMin(Node* node) { while (node && node->left) node = node->left; return node; } Node* remove(Node* node, K key) { if (!node) return nullptr; if (key < node->key) node->left = remove(node->left, key); else if (key > node->key) node->right = remove(node->right, key); else { if (!node->left || !node->right) { Node* temp = node->left ? node->left : node->right; delete node; return temp; } else { Node* minRight = findMin(node->right); node->key = minRight->key; node->value = minRight->value; node->right = remove(node->right, minRight->key); } } return rebalance(node); } public: AVLTree() : root(nullptr) {} void insert(K key, V value) { root = insert(root, key, value); } void remove(K key) { root = remove(root, key); } bool contains(K key) { /* 标准BST查找 */ } V& operator[](K key) { /* 重载[]操作符 */ } };7. 测试与验证
编写测试用例时,我习惯从简单场景开始逐步增加复杂度:
void testAVL() { AVLTree<int, string> tree; // 测试LL型 tree.insert(30, "a"); tree.insert(20, "b"); tree.insert(10, "c"); // 触发右旋 assert(tree.height() == 2); // 测试RR型 tree.insert(40, "d"); tree.insert(50, "e"); // 触发左旋 assert(tree.height() == 3); // 测试LR型 tree.insert(25, "f"); tree.insert(23, "g"); // 触发左右旋 assert(tree.contains(23)); // 测试删除 tree.remove(50); assert(!tree.contains(50)); assert(tree.height() == 3); }实际项目中,还需要测试边界条件如重复插入、删除不存在的键、空树操作等。我通常会使用随机生成的测试数据来验证树的平衡性。