平衡二叉树

导读
大家好,很高兴又和大家见面啦!!!
在上一篇内容中,我们深入探讨了二叉排序树(),了解了它如何通过 "左子树 < 根结点 < 右子树 " 的简单规则实现动态集合的高效查找、插入和删除 。其查询性能理想情况下可以达到优秀的 O ( log n ) O(\log n) O(logn)。
然而,我们也留下了一个关键的悬念:当插入的序列接近有序时 ,二叉排序树会退化成一条"链" ,搜索性能会急剧下降至 O ( n ) O(n) O(n),这与线性表无异,完全丧失了树形结构的优势。
1 2 3 5 6 7 4 1 NULL 2 NULL 3 NULL 4 NULL 5 NULL 6 NULL 7 NULL NULL
那么,我们能否创造一种能够"自我约束 "、始终保持良好身材的二叉排序树呢?答案是肯定的!
今天,我们就来揭开平衡二叉树() 的神秘面纱。它正是为了解决 的"退化"问题而诞生的。 在 的基础上增加了一条简单的"军规":
- 任意结点的左右子树高度差不能超过 1 1 1。
这条规则看似简单,却蕴含着巨大的能量,它能确保整棵树始终维持着"完美"或"接近完美"的平衡,从而将最坏情况下的时间复杂度牢牢锁定在 O ( log n ) O(\log n) O(logn)。
在本篇中,我们将一起学习:
- 是如何通过平衡因子来监控自身平衡状态的。
- 理解"最小不平衡子树" 这一关键概念,它是我们进行修复的精确目标。
- 初步认识当平衡被打破时, 赖以维持平衡的四大"旋转"秘籍 (LL, RR, LR, RL)。
现在,就让我们正式开始,探索这种兼具 的灵活性与稳定高效查询性能的优雅数据结构吧!
一、基本定义
为了避免树的高度增长过快,降低二叉排序树的性能,规定在插入和删除结点时,要保证任意结点的左、右子树高度差的绝对值不超过1,将这样的二叉树称为平衡二叉树 (Balanced Binary Tree ),也称 。
:这个名字的全称为:Adelson-Velsky and Landis Tree (/ˈædəlsən ˈvelski ənd ˈlændɪs triː/)。G.M. Adelson-Velsky 和 E.M. Landis 这两位发明者于1962年的论文中提出该数据结构,因此该数据结构也由这两位提出者的名字命名。
定义结点左子树与右子树的高度差为该结点的平衡因子 ,则二叉树结点的平衡因子的值只可能是 − 1 、 0 、 1 -1、0、1 −1、0、1 这三个值中的一种。
因此平衡二叉树可定义为一棵空树,或是具有以下性质的二叉树:
- 左子树与右子树均为平衡二叉树
- 左子树与右子树的高度差的绝对值不超过1
若我们将各结点的平衡因子存储在各结点中,那么二叉树是否平衡可以体现为以下情况:
- 平衡二叉树
a
1 b
-1 c
1 d
0 e
1 f
0 NULL g
0 NULL
在上述的这棵二叉树中,总共7个结点:
- 结点a:
- 左子树高度为 3 3 3
- 右子树高度为 2 2 2
- 平衡因子:左子树 - 右子树 = 3 − 2 = 1 3 - 2 = 1 3−2=1
- 结点b:
- 左子树高度为 1 1 1
- 右子树高度为 2 2 2
- 平衡因子:左子树 - 右子树 = 1 − 2 = − 1 1 - 2 = -1 1−2=−1
- 结点d:
- 左子树高度为 0 0 0
- 右子树高度为 0 0 0
- 平衡因子:左子树 - 右子树 = 0 − 0 = 0 0 - 0 = 0 0−0=0
可以看到,这棵二叉树的任意一棵子树均为平衡二叉树,且其左右子树的高度差的绝对值也不超过1,因此这棵树为一棵平衡二叉树
- 不平衡二叉树
a
2 b
-1 c
0 d
0 e
0 f
0 g
0
在上述这棵二叉树中,同样有7个结点
- 结点a:
- 左子树高度为: 3 3 3
- 右子树高度为: 1 1 1
- 平衡因子:左子树 - 右子树 = 3 − 1 = 2 3 - 1 = 2 3−1=2
- 结点b:
- 左子树高度为: 1 1 1
- 右子树高度为: 2 2 2
- 平衡因子:左子树 - 右子树 = 1 − 2 = − 1 1 - 2 = -1 1−2=−1
- 结点e:
- 左子树高度为: 1 1 1
- 右子树高度为: 1 1 1
- 平衡因子:左子树 - 右子树 = 1 − 1 = 0 1 - 1 = 0 1−1=0
可以看到虽然该棵树的左右子树均为平衡二叉树,但是其左右子树的高度差的绝对值却大于1,因此该棵树不是一棵平衡二叉树
二、 的插入
二叉排序树保证平衡的基本思想如下:
- 每当在二叉排序树中插入(或删除)一个结点时,首先检查其插入路径上的结点是否因此次操作而导致了不平衡。
- 若导致了不平衡,则先找到插入路径上离插入结点最近的平衡因子的绝对值大于1的结点
a
- 再对以结点
a
为根的子树,在保持二叉排序树特性的前提下,调整各个结点的位置关系,使其重新达到平衡
下面我们通过一个实例来理解该保持平衡的思想;
27
-1 16
0 75
1 38
0 NULL
在上述这棵 中,第一行是结点中存储的关键字的值,第二行是该结点所在的子树对应的平衡因子。
接下来我们需要在树中插入一个元素: 51 51 51 。根据二叉排序树的插入规则,我们会得到下面这棵 :
27
-2 16
0 75
2 38
-1 NULL NULL 51
0
可以看到,此时的 中,有两棵子树的平衡因子的绝对值大于 1 1 1 ,其对应的子树分别为:
- 关键字: 27 27 27 为根结点的子树
27
-2 16
0 75
2 38
-1 NULL NULL 51
0
- 关键字: 75 75 75 为根结点的子树
75
2 38
-1 NULL NULL 51
0
按照二叉排序树保证平衡的基本思想,我们需要找到 离插入结点最近的不平衡子树 (最小不平衡子树 ),也就是上图中以 75 75 75 为根结点的子树;再通过调整该子树中的结点位置,来使其重新达到平衡(具体的调整过程这里我们不展开说明,我们现在只关心最后的调整结果):
75
0 38
0 51
0
上图所示的这棵子树就是我们说的 最小不平衡子树。
当我们完成对该子树的调整后,整棵二叉排序树就被调整为了下面这棵新的 :
27
-1 16
0 75
0 38
0 51
0
可以看到,此时的 又恢复成了一棵 。
PS: 像这种能够将自身自动调整为 的 我们也将其称为 自平衡二叉排序树 (Self-Balancing Binary Search Tree , SBBST)
在 的插入过程中,从空树开始,前半部分的插入与 的插入过程一致,都是通过 左子树 < 根结点 < 右子树 的规则进行新结点的插入。
这里有不太清楚的朋友可以回顾上一篇内容:【数据结构】考研数据结构核心考点:二叉排序树()全方位详解与代码实现 这里我就不再继续展开赘述。
当出现了某个结点插入后,导致整棵树失去平衡时,则需要做出相应的调整。具体的调整规则可以归纳为以下4种:
- LL平衡旋转------最小不平衡子树的根结点的左孩子向右旋转1次
- RR平衡旋转------最小不平衡子树的根结点的右孩子向左选择1次
- LR平衡旋转------最小不平衡子树的根结点的左孩子的右孩子先向左选择1次,再向右旋转1次
- RL平衡旋转------最小不平衡子树的根结点的右孩子的左孩子先向右旋转1次,再向左选择1次
具体我们应该如何判断失衡的 是属于那种调整规则?并且在不同的规则下我们又应该如何调整呢?具体内容,我们将会在下一篇章中详细介绍。
结语
今天的内容到这里就全部结束了。我们来简单回顾一下本次的核心旅程:
我们从一个关键问题出发------普通的二叉排序树 () 在极端情况下会退化成链表,导致性能骤降。
而 ,正是为了解决这一痛点而生的优雅解决方案。它通过引入 平衡因子 这一精妙的"仪表盘",并立下"左右子树高度差绝对值不超过1 "的铁律 ,成功地实现了自我约束。
我们深入理解了:
-
基本定义:明确了是带有平衡约束的二叉排序树。
-
核心思想:掌握了插入结点后,如何通过寻找 最小不平衡子树 来定位问题根源。
-
解决框架:认识了让 具体的调整过程这里我们不展开说明,我们现在只关心最后的调整结果 恢复平衡的四大 "秘籍 "------LL, RR, LR, RL 旋转。
然而,故事才刚刚开始!我们知道了有四大 "武功秘籍 "可以"拨乱反正 ",但最关键的一步还悬而未决:
- 面对一棵刚刚失衡的AVL树,我们究竟该如何一眼诊断出它属于哪种失衡类型?
- 每一种旋转的具体操作步骤又是怎样的?
这些看似复杂的旋转操作,背后其实隐藏着一条可以轻松驾驭的、统一的逻辑链。 掌握了它,你就能在眨眼间判断出应对策略。
这一切的答案,以及完整的代码实现,都将在下一篇内容中为你彻底揭晓!
感谢阅读,欢迎用以下方式支持继续创作:
👍 点赞支持 - 您的认可,是我持续创作清晰易懂教程的最大动力!
⭐ 收藏文章 - 将这篇 核心框架指南收藏起来,方便日后随时查阅,为下一篇深入理解旋转操作打下坚实基础。
🔁 转发分享 - 如果您有朋友也在学习数据结构,欢迎推荐给TA,大家一起进步。
💬 评论交流 - 关于平衡因子或最小不平衡子树,您还有哪些疑问?最期待下篇详解哪种旋转?欢迎在评论区留言!
感谢各位的支持,我们下一篇,不见不散!
*[BST]: Binary Search Tree,二叉搜索树/二叉排序树
*[AVL树]: Adelson-Velsky and Landis Tree,自平衡二叉排序树