跳转到主内容
极星编程网:以代码为星,赴技术山海!

C++实现平衡二叉树AVL的自平衡调整 _ LL/RR旋转代码逻辑【源码】

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,就全乱了。 立即学习 “ C++免费学习笔记(深入) ”; 推荐统一用
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
),但初学者更容易在多层递归中搞混引用绑定对象 实际写的时候,最难绷的是高度更新时机和空节点高度定义,这两处一错,整棵树的平衡逻辑就不可信了。

相关文章