AVL树旋转必须返回新根节点,因二叉链表无父指针,调用方需用返回值重连子树;height函数对空节点须返回−1以保证平衡因子计算正确;四种失衡类型严格对应单旋或双旋,且判断条件中需包含等号。

AVL树的平衡旋转不是“左右各写一个函数就行”,关键在旋转后要同步更新高度、修正父指针(如果用三叉链)、且单次旋转未必够——插入后可能需从插入点向上逐层检查并最多执行一次旋转。
为什么rotateLeft和rotateRight必须返回新根节点
AVL节点通常用二叉链表实现,没有父指针。旋转会改变子树根节点,上层调用处必须用返回值重新链接。比如插入右子树导致失衡,调用rotateLeft后,原父节点的right字段必须指向新根,否则链断裂。
常见错误:函数声明为void rotateLeft(Node* node),内部改了node->right和node->right->left,但调用方仍用旧指针,整棵子树丢失。
正确做法:
-
Node* rotateRight(Node* y):传入失衡节点y,返回旋转后的新根(即原y->left) - 调用后必须赋值:例如
x->right = rotateLeft(x->right) - 旋转内所有指针重连完成后,立即调用
updateHeight更新涉及节点的高度
getBalanceFactor的计算顺序与边界处理
平衡因子 = 左子树高度 − 右子树高度。很多实现直接写height(node->left) - height(node->right),但若node为nullptr,height必须返回−1(不是0),否则叶子节点平衡因子算成1而非0。
示例错误逻辑:
int height(Node* n) { return n ? n->height : 0; } // ❌ 导致叶子节点 balance = 1−0 = 1
正确写法:
int height(Node* n) { return n ? n->height : -1; } // ✅ 叶子节点:0−0 = 0
因此getBalanceFactor应严格基于此定义,且不能省略空指针判断——哪怕你确定非空,编译器优化或调试路径也可能触发未定义行为。
四种失衡场景如何对应到旋转组合
AVL只存在四种失衡类型,由插入位置和当前节点平衡因子共同决定,不能凭感觉选旋转:
-
LL型:
balance > 1 && getBalanceFactor(node->left) >= 0→ 单次rotateRight -
RR型:
balance right) → 单次rotateLeft -
LR型:
balance > 1 && getBalanceFactor(node->left) → 先rotateLeft(node->left),再rotateRight(node) -
RL型:
balance right) > 0→ 先rotateRight(node->right),再rotateLeft(node)
注意:>= 0和包含等于0的情况,因为插入可能让原本平衡的子树变+1或−1,仍属LL/RR;而或> 0才触发双旋。漏掉等号会导致部分失衡被忽略。
插入后updateHeight和平衡检查的调用时机
高度更新必须在旋转完成之后、向上回溯之前进行。典型错误是:在递归插入返回后立刻更新当前节点高度,再检查平衡——此时子树可能已失衡,但高度却是“失衡前”的值,导致getBalanceFactor误判。
正确流程(以递归插入为例):
- 递归插入左/右子树
- 更新当前节点
height = 1 + max(height(left), height(right)) - 计算
balance = getBalanceFactor(this) - 根据
balance和子节点balance决定是否旋转 - 旋转后,新根高度已由旋转内
updateHeight修正;原节点若被降级为子节点,其高度也在旋转中更新
最容易被忽略的一点:旋转函数内部必须更新所有涉及节点的高度(至少3个节点),且顺序不能错——先更新孙子,再更新子,最后更新当前节点,否则中间计算会出错。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











