ACM 选手带你玩转平衡二叉树

255 阅读11分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第28天,点击查看活动详情


大家好呀,我是平衡蛋~

在上一个阶段,我带大家玩转了二叉搜索树,那在这个阶段我来和大家一起搞一下平衡二叉树。

如果你对平衡二叉树一点儿也不了解的话,你可能会感觉我从二叉搜索树转折到平衡二叉树有点生硬。

phecsrm1-0

平衡二叉树怎么来的

在这呢,我就先透漏一下平衡二叉树的半个概念,顺便也是解答上面说的平衡二叉树和二叉搜索树的关系:

其实叭,平衡二叉树也是一棵二叉搜索树

phecsrm1-2

那现在可能又有一个疑问,为啥有了二叉搜索树还要整出个平衡二叉树?

那就得从盘古开天辟地,呃,从二叉搜索树的一个特殊情况说起...

如果你看过我二叉搜索树的实战文章,在分析递归题解时间复杂度的时候我一般会说:xx 大小取决于二叉搜索树的高度,二叉搜索树最坏情况下的高度为 n。

问题就出在这个高度为 n 上。

比如对于序列 root = [1,2,3,4],正常情况下,我们构造的应该是下面这样的:

phecsrm1-3

但总有不正常的时候,比如在老年痴呆的的情况下构造出的二叉搜索树可能是下面这样的:

phecsrm1-4

你看这像啥样子,好好一棵二叉搜索树非得 cos 个”单链表“。

phecsrm1-5

这就造成了,本来我查找一个节点的时间复杂度度是 O(logn),现在硬生生的成了 O(n)。

比如我正常情况下找 4 这个节点,找 2 次就够,而在 cos 情况下,我得找 4 次,这种现象就是二叉搜索树的退化。

从上面两张不同二叉搜索树的图可以看出,树的高度决定了它的查找效率,那我们是不是可以这么考虑:当树的高度最小时,就能够保证树的查找效率

phecsrm1-6

连我都能考虑出来的东西,显然大佬们早就解决了,当然了也没很早,也就区区...60 年叭~

1962 年,两个数学家 AV + L 提出了平衡二叉树的概念。

哎呀妈呀,终于套上了,至于平衡二叉树到底是个什么东西,我们下...面分解。

phecsrm1-7

平衡二叉树

我在上一节中说了平衡二叉树的半个概念,现在来把它补充完整。

平衡二叉树,是一种二叉排序树,其中对于每个节点的左右子树高度相差小于等于 1。

后半句点出了平衡二叉树中”平衡“的实际指向,即”高度平衡“!

把二叉树左右子树的高度看成是左右子树的重量,把这两个重量放在天平上,尽可能保证这个天平是平衡的。

phecsrm1-8

那怎么确定什么程度是”尽可能的平衡“呢?这里就引出一个概念:平衡因子(BF,Balance Factor)。

它的计算公式:平衡因子 = 左子树的高度 - 右子树的高度

那对于平衡二叉树来说,它概念中”每个节点的左右子树高度相差小于等于 1“,这么来看,平衡二叉树的平衡因子只可能是 1,0,-1。

phecsrm1-9

也可以这么说,只要你的平衡因子的绝对值 > 1,那你就不是平衡二叉树。

由此,我们在判断一棵二叉树是不是平衡二叉树,2 个条件

  1. 这棵二叉树是二叉搜索树。
  2. 这棵二叉树的每个节点的 BF 绝对值 < 1。

比如下图:

phecsrm1-10

这就不是一棵平衡二叉树,它首先就不满足条件 1:是一棵二叉搜索树。

比如下图:

phecsrm1-11

这也不是一棵平衡二叉树,它虽然满足条件 1 是一棵二叉搜索树,但是不满足条件 2,对于节点 3 来说,它的 BF = 2。

比如下图:

phecsrm1-12

这就是一棵平衡二叉树,它同时满足条件 1 和条件 2。

平衡二叉树就通过这种“尽可能的平衡”,让树的高度保持在 logn,这是相对最小的树高,也正因为是维持了这个树高,对于一棵有 N 个节点的平衡二叉树来说,它每一个操作的时间复杂度都可以维持在 O(logn)

对于平衡二叉树的操作主要有 3 种:

  • 查找操作
  • 插入操作
  • 删除操作

查找操作我就不在这多说了,和二叉搜索树的查找一个套路。

ACM 选手带你玩转二叉搜索树!

下面我重点来讲下平衡二叉树的插入和删除操作,这个一定要仔细看,重点看!

插入操作

对于一棵平衡二叉树来说,插入一个新的节点可能会让平衡二叉树【失衡】。

比如对于下面这棵平衡二叉树:

phecsrm1-13

当插入节点 7 的时候,成了下面这样:

phecsrm1-14

此时节点 5 的 BF = -2,这就造成了平衡二叉树的失衡。

这里就会产生一个概念,叫做【最小不平衡子树】,即距离插入节点最近的,且平衡因子绝对值大于 1 的节点为根的子树

在上图中,距离插入节点 7 最近的 BF 的绝对值大于 1 的节点就是节点 5,所以以节点 5 为根节点的二叉树就是最小不平衡子树。

phecsrm1-15

那平衡二叉树失衡了,就要想办法让它重新平衡,这种失衡的调整主要就是通过调整最小不平衡子树来实现重新平衡

这里的调整靠的是【旋转】,通过旋转让二叉树重新成为新的平衡二叉树。

根据旋转的方向不同,主要分为 2 种:

  • 左旋
  • 右旋

左旋

左旋操作主要是把最小不平衡子树的右孩子作为根进行旋转操作,具体步骤为:

  1. “根节点的右孩子”作为“新的根节点”
  2. “根节点的右孩子的左子树”(如果有)变为“根节点的右子树”
  3. “根节点”变为“根节点的右孩子”的左子树

可能看着比较绕...

比如对于下图:

phecsrm1-16

最小不平衡子树的根节点为 5,根节点的右孩子为 6,根节点的右孩子的左子树为 null,所以经过左旋,变成了下面的样子:

phecsrm1-17

右旋

与左旋对称的就是右旋,右旋是把最小不平衡子树的左孩子作为根进行旋转操作,具体步骤为:

  1. “根节点的左孩子“作为“新的根节点”
  2. ”根节点的左孩子的右子树“(如果有)变为”根节点的左子树“
  3. “根节点”变为“根节点的左孩子”的右子树

比如对于下图:

phecsrm1-18

最小不平衡子树的根节点为 3,根节点的左孩子为 2,根节点的左孩子的右子树为 null,所以经过右旋,变成了下面这个样子:

phecsrm1-19

不同插入节点的方式的不同旋转

对于平衡二叉树的节点 node 来说,有 4 种插入方式:

  • 插入左孩子的左子树(LL 型)
  • 插入右孩子的右子树(RR 型)
  • 插入左孩子的右子树(LR 型)
  • 插入右孩子的左子树(RL 型)

这些插入方式可能会造成 node 的 BF 绝对值大于 1,从而打破了原平衡二叉树的平衡。

针对这些不同的插入方式有着不同的旋转办法,下面我们一一来看。

LL 型失衡

针对插入左孩子的左子树失衡,只需要执行一次【右旋】即可。

phecsrm1-20

插入节点 1,造成失衡,这时候最小不平衡子树的根节点为 3,直接进行一次右旋操作。

phecsrm1-21

RR 型失衡

对于针对插入右孩子的右子树失衡,只需要执行一次【左旋】即可。

phecsrm1-22

插入节点 7,造成失衡,这个时候最小不平衡子树的根节点为 5,直接进行一次左旋操作。

phecsrm1-23

LR 型失衡

插入左孩子的右子树失衡,这就不是 1 步能解决的事了...

这个时候需要 2 步:

  1. 对最小不平衡子树根节点的左孩子左旋
  2. 最小不平衡子树右旋

phecsrm1-24

插入节点 2,造成失衡,最小不平衡子树的根节点为 3。

第 1 步,此时最小不平衡子树根节点的左孩子为节点 1,将以节点 1 为根节点的子树左旋。

phecsrm1-25

第 2 步,将最小不平衡子树右旋。

phecsrm1-26

RL 型失衡

当然,插入右孩子的左子树失衡,也不是 1 步能解决的事,同样也需要 2 步:

  1. 对最小不平衡子树根节点的右孩子右旋
  2. 最小不平衡子树左旋

phecsrm1-27

插入节点 6,造成失衡,最小不平衡子树的根节点为 5。

第 1 步,此时最小不平衡子树根节点的右孩子为节点 7,将以节点 7 为根节点的子树右旋。

phecsrm1-28

第 2 步,将最小不平衡子树左旋。

phecsrm1-29

那这 4 种情况我是都讲完了,情况就是这么些情况,说麻烦其实也不麻烦,就是绕点儿。

趁看到现在还明白点,赶紧自己动手画几遍。

phecsrm1-30

删除操作

对于一棵平衡二叉树来说,它的删除情况和二叉搜索树一样,同样是分 3 种情况:

  • 删除节点为叶子节点
  • 删除的节点只有左子树或者右子树
  • 删除的节点既有左子树又有右子树。

稍微有点区别的是删除完节点后,重新判断一下是否依然平衡,如果失衡,旋转操作就走起来。

这里需要注意的是,删除节点以后,可能存在多个不平衡的节点。

phecsrm1-31

平衡二叉树因为删除节点判断失衡,我们可以换一种想法来理解:

  • 删除的是左子树的节点,那左子树的高度 -1,这可以想成,左子树高度不变,右子树高度 +1,即相当于在右子树插入了一个新的节点。
  • 同理,删除的是右子树的节点,那右子树的高度 -1,这也可以想成,右子树高度不变,左子树高度 + 1,相当于在左子树上插入了一个新的节点。

下面我简单介绍一下 3 种情况,至于更细节的内容我们在后面的实战文章中继续。

情况 1:删除节点为二叉树的叶子节点

第 1 步 删除:

通过平衡二叉树的查找操作找到节点,然后直接删掉就好了

第 2 步 旋转:

从删除节点的父节点开始判断是否失衡,如果失衡,则判断是 LL、RR、LR、RL 中的哪种失衡,根据上面讲的调整方式进行旋转。

如果不失衡,就继续向父节点的父节点继续判断...

情况 2 :删除的节点只有左子树或者右子树

第 1 步 删除:

直接用删除节点的父节点指向它的子树即可

第 2 步 旋转:

从删除节点的父节点开始判断是否失衡,如果失衡,则判断是 LL、RR、LR、RL 中的哪种失衡,根据上面讲的调整方式进行旋转。

如果不失衡,就继续向父节点的父节点继续判断...

情况 3:删除的节点既有左子树又有右子树

第 1 步 删除:

这个就稍微复杂点了,就是将要删除节点 node 位置上替换成左子树最右边的节点或者右子树最左边的节点。

即左子树的最大值或者右子树的最小值。

第 2 步 旋转:

删除以后的调整操作,还是与情况一和情况二的旋转操作一样调整。

phecsrm1-32


平衡二叉树入门基础到这就讲完啦,哎呀妈呀,感觉写了好久,肩膀都写硬了...

平衡二叉树这玩意怎么说呢,看着挺难的,实际就是稍微复杂点,就是一步一步的拆出来看,就还好。

相信我在文章中已经讲的很明白了,认真看完的同学一定看懂了,如果你要说你没看懂,那...就再看一遍~

phecsrm1-33

接下来就是通过实战题来巩固啦~

我是帅蛋,我们下次见啦!