AVL树旋转前必须先计算平衡因子,平衡因子=左子树高度−右子树高度,绝对值大于1才触发旋转;插入/删除后需自底向上更新高度(空节点高度为-1)和平衡因子,并依据失衡节点及其子节点的平衡因子组合判断LL/LR/RR/RL四种旋转类型。

AVL树旋转前必须先计算平衡因子
不计算 balance_factor 就直接旋转,等于蒙眼调参——你根本不知道该不该转、往哪边转。AVL树的旋转触发条件是某个节点的平衡因子绝对值大于1,而平衡因子 = 左子树高度 − 右子树高度。很多初学者直接拿BST插入逻辑套用,漏掉这一步,结果树看起来“插进去了”,但 is_balanced 始终返回 False。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 每次插入/删除后,从修改点向上回溯到根,逐层更新高度并计算
balance_factor - 用递归后序遍历最自然:先处理左右子树,再算当前节点高度和因子
- 高度更新别写成
node.height = max(left.height, right.height) + 1—— 如果子节点为空,它的height必须是-1(不是0),否则因子全乱
四种旋转场景由失衡节点和插入路径共同决定
单旋还是双旋,不只看失衡节点的因子,更要看新节点最终落在哪一支:LL、LR、RR、RL。常见错误是只查失衡节点因子(比如 bf == -2),就默认右旋,结果在LR型里硬右旋,树反而更歪。
实操建议:
立即学习“Python免费学习笔记(深入)”;
-
bf == 2且左孩子的bf >= 0→ LL → 右旋 -
bf == 2且左孩子的bf == -1→ LR → 先对左孩子左旋,再对当前节点右旋 -
bf == -2且右孩子的bf → RR → 左旋 -
bf == -2且右孩子的bf == 1→ RL → 先对右孩子右旋,再对当前节点左旋
注意:bf == 0 的子节点在LR/RL判断中必须参与检查——它说明插入发生在“外侧”,不能跳过。
旋转后必须重置涉及节点的高度
旋转只是改指针,不更新 height 字段,后续所有平衡计算都会错。典型表现:第一次插入触发旋转后树正常,第二次插入直接崩溃,报错 AttributeError: 'NoneType' object has no attribute 'height'——因为旧高度残留导致某次高度计算传入了空节点。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 每种旋转函数(
rotate_right、rotate_left)末尾,必须显式更新被旋转的两个节点的高度 - 例如右旋后:
new_root.height = max(get_height(new_root.left), get_height(new_root.right)) + 1,然后才是node.height = max(get_height(node.left), get_height(node.right)) + 1 - 写个辅助函数
get_height(node)统一处理空节点返回-1,别在每个高度计算里重复判空
删除操作比插入更容易引发多层失衡
插入最多导致一条路径上1次旋转;删除可能让父节点、祖父节点连续失衡,必须从删除点一路向上检查并旋转。很多人只处理第一个失衡点就 return,结果树下半部分看着平衡,上半部分倾斜严重。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 删除后不要提前终止——即使某层旋转恢复了平衡,仍要继续向上回溯,因为高度变化可能让更高层也失衡
- 旋转后当前节点高度大概率改变,必须重新计算其父节点的平衡因子,不能沿用旧值
- 如果用迭代方式回溯,记得保存父节点引用;若用递归,确保递归返回的是修正后的子树根节点
真正麻烦的不是旋转本身,而是高度传播的链式反应——一个叶子删掉,可能迫使根节点都得重算高度。别省那几行代码,老老实实走完整条路径。


















