AVL树插入后必须在递归回溯中实时更新高度、计算平衡因子,并在BF=±2时立即旋转,且将新子树根赋给父节点对应指针;需统一约定空节点高度,安全调用get_height避免空指针。

AVL树插入后失衡,不能靠“最后统一检查根节点”来修复——必须在递归回溯途中,对每个受影响的节点实时更新高度、计算平衡因子,并在发现失衡(BF = 2 或 −2)时立即旋转,且必须把旋转后的新子树根赋值给上层节点的对应指针(left 或 right),否则旋转形同虚设,树实际已退化为链表。
平衡因子与高度更新必须同步进行
每个节点需显式维护 height 字段(空节点高度为 0 或 −1,需统一约定);平衡因子 BF 定义为 左子树高度 − 右子树高度(部分实现用右减左,但逻辑一致,关键在自洽)。插入递归返回后,必须立刻执行:
- 更新当前节点 height = 1 + max(左子树高度, 右子树高度)
- 计算 BF = get_height(node.left) − get_height(node.right)
- 若 BF 超出 [−1, 1],说明该节点失衡,需旋转
四种旋转场景判定要结合插入路径
仅看当前节点 BF 不够,还需观察新节点插入到了哪一支的哪一侧。典型判定逻辑(以 BF = 左−右 为例):
- BF = 2 且新节点插在左子节点的左子树 → LL型 → 单右旋
- BF = 2 且新节点插在左子节点的右子树 → LR型 → 先左旋左子树,再右旋当前节点
- BF = −2 且新节点插在右子节点的右子树 → RR型 → 单左旋
- BF = −2 且新节点插在右子节点的左子树 → RL型 → 先右旋右子树,再左旋当前节点
注意:判断“插在哪一侧”应基于插入值 key 与子节点 key 的比较结果,而非仅看子树结构。
旋转操作本质是局部拓扑重构
所有旋转都只改变至多三个节点之间的父子关系和指针指向,不修改键值。例如右旋(RR→LL):
- 设 parent 为失衡节点,subL = parent.left
- 令 parent.left = subL.right
- 令 subL.right = parent
- 更新 subL 和 parent 的 height(顺序不能错)
- 返回 subL —— 它才是旋转后该子树新的根
双旋只是两次单旋的组合,中间节点的高度更新不可省略。
空指针安全是高频陷阱
调用 get_height(node.left) 前,node.left 可能为 null。直接访问 node.left.height 会抛异常。正确写法是:
def get_height(node): return node.height if node else 0同理,get_balance(node) 中对左右子树调用 get_height 时,也必须容忍 None 输入。这是多数初学者调试数小时才发现的隐性崩溃点。










