[数据结构·树] 平衡二叉搜索树(AVL)
🌳 AVL 树 (自平衡二叉查找树) 的 C 语言实现与解析
AVL 树是一种自平衡的二叉查找树(BST)。它通过在插入和删除节点后进行“旋转”操作,来确保树的高度始终保持在 $O(\log n)$ 级别,从而保证了所有基本操作(查找、插入、删除)的最坏时间复杂度都是 $O(\log n)$。
📄 完整的 C 语言实现代码
1 |
|
🔑 关键代码解析
AVL 树的核心在于 insert 和 deleteNode 函数中的平衡维护部分。
1. height() 和 getBalanceFactor()
这是 AVL 树的基础。
height(struct Node* N):这个辅助函数用于安全地获取一个节点的高度。
关键点: 它必须能处理
NULL节点。我们将NULL节点的高度定义为0(有些实现定义为 -1,但 0 更便于计算)。叶子节点的高度因此为1。
getBalanceFactor(struct Node* N):计算平衡因子 (BF),这是 AVL 树的核心指标。
$BF = height(N \rightarrow left) - height(N \rightarrow right)$
平衡条件: 对于一棵 AVL 树,所有节点的 $BF$ 必须在
[-1, 0, 1]这个范围内。如果 $BF > 1$,意味着左子树比右子树“重”太多(左倾)。
如果 $BF < -1$,意味着右子树比左子树“重”太多(右倾)。
2. 旋转操作:leftRotate() 和 rightRotate()
旋转是恢复平衡的唯一手段。它们在 $O(1)$ 时间内完成,并且保持了二叉查找树的有序性。
rightRotate(y):当节点
y因为其左子节点x(Left)而变得不平衡时使用。它将
x提升为新的根,y降为x的右子节点。x原本的右子树 (T2) 变成了y的新左子树。重要: 必须先更新
y的高度,再更新x的高度,因为x的新高度依赖于y的新高度。
leftRotate(x):当节点
x因为其右子节点y(Right)而变得不平衡时使用。它将
y提升为新的根,x降为y的左子节点。y原本的左子树 (T2) 变成了x的新右子树。
3. insert(node, data) 插入操作
这是最关键的部分。它分为 4 个步骤:
标准 BST 插入:
- 使用递归,像普通二叉查找树一样找到
NULL位置,并插入新节点。
- 使用递归,像普通二叉查找树一样找到
更新高度:
在递归返回(回溯)的路径上,更新从新节点到根节点路径上所有祖先节点的高度。
node->height = 1 + max(height(node->left), height(node->right));
获取平衡因子:
int balance = getBalanceFactor(node);
检查并执行旋转(四种情况):
在回溯路径上,我们检查第一个不平衡的节点(即
balance > 1或balance < -1的节点)并进行修正。a. 左-左 (LL) 情况 (balance > 1)
触发: 在
node的左 (L)子树的左 (L)边插入了新节点。判断:
balance > 1且data < node->left->data。修正: 对
node执行一次右旋转 (rightRotate(node))。
b. 右-右 (RR) 情况 (balance < -1)
触发: 在
node的右 (R)子树的右 (R)边插入了新节点。判断:
balance < -1且data > node->right->data。修正: 对
node执行一次左旋转 (leftRotate(node))。
c. 左-右 (LR) 情况 (balance > 1)
触发: 在
node的左 (L)子树的右 (R)边插入了新节点。判断:
balance > 1且data > node->left->data。修正(两步):
node->left = leftRotate(node->left);(先对其左子节点左旋,将其变为 LL 情况)return rightRotate(node);(再对node右旋)
d. 右-左 (RL) 情况 (balance < -1)
触发: 在
node的右 (R)子树的左 (L)边插入了新节点。判断:
balance < -1且data < node->right->data。修正(两步):
node->right = rightRotate(node->right);(先对其右子节点右旋,将其变为 RR 情况)return leftRotate(node);(再对node左旋)
4. deleteNode(root, data) 删除操作
删除操作比插入更复杂,因为它可能导致沿途多个节点失衡(而插入最多只会导致一个节点失衡)。
标准 BST 删除:
和我们之前实现的 BST 删除逻辑一样。
找到节点,分 0、1、2 个子节点三种情况处理。
如果是 2 个子节点,用中序后继(右子树的最小值)替换它,然后递归地删除那个后继节点。
处理树为空:
- 如果删除的是最后一个节点 (
root == NULL),必须立即返回,否则后续的高度更新会访问空指针。
- 如果删除的是最后一个节点 (
更新高度:
- 同插入操作。
检查并执行旋转:
这一步的逻辑与插入类似,但判断条件略有不同。我们不再依赖
data(因为data已经被删了或替换了),而是直接检查子节点的平衡因子。LL 情况:
balance > 1且getBalanceFactor(root->left) >= 0。LR 情况:
balance > 1且getBalanceFactor(root->left) < 0。RR 情况:
balance < -1且getBalanceFactor(root->right) <= 0。RL 情况:
balance < -1且getBalanceFactor(root->right) > 0。注意: 因为删除操作可能导致多个祖先节点失衡,所以不需要在找到第一个失衡点后就停止,递归的回溯过程会自动修正所有路径上的失衡。
