LL旋转必须先保存新根并处理原左子节点的右子树,再连接父节点,否则丢失子树;需及时更新高度,空节点高度为-1,平衡因子为左右高度差,绝对值大于1才旋转。

LL旋转为什么必须先改子树再连父节点
LL旋转本质是右旋,但直接 root = root->left 会丢掉原左子节点的右子树。正确顺序是:先保存 new_root = root->left,再把 new_root->right 挂到 root->left 上,最后才让 root 成为 new_root 的右孩子。漏掉中间这步,root->left->right 就彻底丢失了。
常见错误现象:segmentation fault 或插入后树结构错乱,尤其在连续插入递增序列(如 1,2,3,4)时暴露明显。
- RR旋转同理,只是方向相反:先保存
new_root = root->right,再把new_root->left接给root->right - 旋转后必须重新计算高度,否则后续平衡因子判断全失效 —— 别忘了调用
updateHeight() - 如果
root是整棵树的根,旋转后要返回新根;如果是子树,上层递归需用返回值更新对应指针(比如node->left = rotateLL(node->left))
平衡因子怎么算才不越界
AVL 节点的平衡因子定义为 height(left) - height(right),但很多人直接用 abs() 判断是否失衡,这是错的。失衡只发生在值为 2 或 -2 时,abs(bf) > 1 才触发旋转 —— 写成 abs(bf) >= 2 会导致多旋转,破坏树结构。
更隐蔽的问题是高度计算:空节点高度应为 -1(不是 0),否则单个节点的平衡因子会是 0,但左右都为空时 0 - 0 = 0 是对的;而一个节点带左孩子时,若空节点高为 0,则 1 - 0 = 1,没问题;但若误设空节点高为 1,就全乱了。
- 推荐统一用
getHeight(Node* n) { return n ? n->height : -1; } - 每次插入/删除后,从修改点向上回溯更新高度,不能只更新当前节点
- 平衡因子不要缓存为成员变量,每次用时实时计算,避免因疏忽未更新导致逻辑错判
LR/RL旋转为什么不能拆成两次单独LL+RR
可以拆,但必须注意中间节点的高度和平衡因子状态。比如 LR 旋转:先对 root->left 做 RR,再对 root 做 LL。问题在于,第一次 RR 后,root->left 的高度可能变化,如果不立即更新,第二次 LL 用的还是旧高度,平衡因子计算就偏了。
典型表现:插入序列 3,1,2 后本该形成平衡树,结果因高度未及时更新,第二次 LL 判定失败,树仍不平衡。
- 每次单旋转后必须立刻调用
updateHeight()更新涉及的三个节点(新根、原根、孙子节点) - LR 和 RL 是原子操作,代码里建议封装成独立函数,别在插入逻辑里手写两步旋转
- 如果用递归插入,旋转函数返回新子树根,上层直接赋值,避免指针悬空
insert() 返回 Node* 是为了什么
因为 AVL 插入可能引发自底向上的一系列旋转,最顶层的旋转会改变子树根节点。如果 insert() 是 void,上层无法拿到新根,就会继续用旧指针操作,造成内存访问错误或树断裂。
例如:向只有根节点的树插入一个导致 LL 失衡的值,旋转后原根变成右孩子,新左孩子成了根 —— 这个新根必须由 insert() 返回,并被父调用方赋给对应左/右指针。
- 所有递归调用都要接收返回值:
root->left = insert(root->left, val); - 即使没旋转也要返回
root,保持接口统一 - 如果不习惯返回指针,可以用引用参数(
Node*& root),但初学者更容易在多层递归中搞混引用绑定对象
实际写的时候,最难绷的是高度更新时机和空节点高度定义,这两处一错,整棵树的平衡逻辑就不可信了。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











