AVL树节点必须显式包含height字段且初值为1,插入删除后需自底向上更新祖先高度并根据平衡因子执行LL/RR/LR/RL旋转,旋转函数须返回新根并覆盖原指针,删除双子节点时应从中序后继原父节点开始回溯修复。

AVL树节点定义必须包含高度字段
Go 语言没有继承和泛型(旧版),所以不能像 C++ 那样靠模板复用逻辑,必须显式在节点结构体里存 height。漏掉这个字段,所有旋转和平衡判断都会失效。
常见错误是只存 left/right/val,结果 getBalanceFactor 返回零或随机值,插入后树完全不调整。
-
height初始值为 1(单节点高度为 1),不是 0 - 每次插入/删除后,必须自底向上更新路径上所有祖先的
height,不能只更新根 - 推荐把高度更新封装成独立函数:
updateHeight(node *Node),避免重复写max(leftH, rightH) + 1
四种旋转操作要严格区分 LL/LR/RR/RL 场景
AVL 的核心是根据平衡因子(left.height - right.height)和子树失衡方向决定旋转类型。Go 里没指针运算,全靠结构体指针赋值,稍不注意就会漏掉父节点链接或搞反左右子树。
典型翻车点:LR 型误做成单次右旋,结果树更不平衡;或者旋转后没重置某子节点的 parent(如果实现含父指针)。
立即学习“go语言免费学习笔记(深入)”;
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- LL:左子树的左子树过高 → 右旋
- RR:右子树的右子树过高 → 左旋
- LR:左子树的右子树过高 → 先对左子节点左旋,再对当前节点右旋
- RL:右子树的左子树过高 → 先对右子节点右旋,再对当前节点左旋
- 旋转函数返回新根节点,调用处必须用返回值覆盖原指针:
root = rotateRight(root)
插入后回溯更新高度并检查平衡需用递归返回值传递新根
Go 没有引用传参,函数内修改 *Node 的 left/right 字段不会自动影响上层调用者的变量。所以插入逻辑必须返回当前子树的新根,否则旋转结果丢失。
例如:在 insert(node, val) 中对 node.left 插入后,必须写 node.left = insert(node.left, val),而不是只调用 insert(node.left, val)。
- 每层递归结束前,先更新
node.height,再算getBalanceFactor(node) - 若平衡因子绝对值 > 1,立即执行对应旋转,并返回旋转后的新根
- 不要在旋转后继续向下递归——旋转已重排局部结构,后续逻辑由新根的递归调用覆盖
删除节点后平衡修复比插入更易出错
删除分三种情况(无子、单子、双子),其中双子节点替换时,通常用中序后继(右子树最左节点)。但替换后,那个后继节点本身可能失衡,必须从它原来的位置开始向上修复,而不是从原删除点。
很多人只从原 root 开始回溯,导致后继节点所在子树的失衡被跳过,树逐渐退化。
- 找到中序后继后,保存其原始父节点(用于后续回溯起点)
- 用后继值覆盖待删节点,然后真正删除后继节点
- 从后继节点的原父节点开始,逐层向上更新高度并检查平衡
- 删除后的平衡修复逻辑和插入一致,但触发位置不同,不能复用同一段“从 root 回溯”代码
实际写起来,height 字段、旋转返回值、回溯起点这三点,任一遗漏都会让树在几次增删后彻底失去平衡。调试时可加 printTreeWithHeight() 辅助输出每层高度,比纯看结构更早发现问题。

















