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