算法笔记-6:平衡二叉树(理论篇)

上期我们说到,如果按从小到大的顺序向二叉搜索树插入节点,那么搜索树会退化成链表,这样就背离我们利用二叉搜索树的特性方便查询元素的初衷了。

有没有办法让这个比萨斜塔一样的结构变成一棵左右平衡的二叉搜索树呢?

图片

图1-退化成链表的二叉搜索树

不如,把链表打个对折?

图片

图2-链表对折

感觉像搭了个人字梯,中间还是太空了……

图片

没事,注意到20左右两边分别可以视为链表,继续对折。

图片

图3-继续折叠

这种方法看起来对链表效果不错,但是对于更一般的失衡树,就很难发挥作用了,会遇到大量的子树冲突问题。

图片

图4-几种非链表失衡树

与其将不平衡的二叉树调整成平衡,不如一开始就构造一个平衡的二叉搜索树。每新增一个节点,就判断并重新调整平衡。这便是构造平衡二叉树的过程。

构造平衡二叉搜索树(又称自平衡二叉搜索树)并不只有一种方法,但我们常说的平衡二叉树一般指AVL树。AVL树是最早被发明的平衡二叉树,由Adelson Velsky 和 Evgenii Landis在1962年发表的论文中提出,并以二人的名字命名。这也是我们今天将要介绍的内容。


一、平衡二叉搜索树的定义

如果一棵二叉搜索树中,任何一个节点的左右子树**高度(深度)**差不超过1,那么它就是一棵平衡二叉搜索树。

图片

图5-平衡二叉树

换句话说,AVL树的平衡,是通过限制左右子树的****高度差来实现的。所以AVL树又被称为高度平衡树

来看看如下哪些属于平衡二叉树?

图片

图6

(a)不是平衡二叉树,虽然根节点左右子树高度一样,但子节点子树一边高度为2,一边为0。

(b)不是平衡二叉树,虽然根节点是平衡的,右边只比左边低1层。但左子树自身仍是失衡的。

(c)平衡二叉树。

(d)不是平衡二叉树。根节点失衡,左子树高为3,右子树为1。

二、平衡因子

三四层高的树,就很容易眼花分不清平不平衡了。要是树有20多层,一层层数不得累坏去?构造平衡树,判断平不平衡的工作最终还是要交给程序来办。为了使程序能够判断二叉搜索树的平衡性,Adelson Velsky 和 Evgenii Landis在论文里提出了平衡因子的概念:

节点的左子树与右子树的高度(深度)差即为该节点的平衡因子。

图片

图7-平衡因子计算

对于程序来说,每个节点记录以自己的高度,然后把两个子节点上报的高度相减(缺失的子节点高度设为0),就可以得到平衡因子。

一棵空树肯定是平衡的,一棵只有根节点的树和一棵只有一个根和一个子节点的树也是平衡的。再往后,不论树中有多少个节点,一定是插入节点导致失衡,或者删除节点时导致失衡。如果我们每次插入或者删除节点后,都通过一定处理使树重新恢复平衡,那么最终一定能得到平衡二叉树。这就是构造平衡二叉树的原理。


三、插入节点导致失衡时的调整

假如我们要在图5的节点60下面插入节点70,先来计算插入前每个节点的平衡因子,标在节点旁边:

图片

图8-插入前的平衡情况

插入节点70后,部分节点的平衡因子发生了变化:

图片

图9-插入后的平衡情况

如果沿着新插入的节点70不断地访问父节点,直到找到第一个平衡因子绝对值大于1的节点,也就是40。那么,以40这个节点为根节点的子树,就被称为最小失衡子树。

如果把整棵树调整成平衡树太难,那么我们就先调整这棵最小失衡子树。而调整平衡,需要用到旋转方法,至于怎么旋转,则需要分四种情况讨论:

1、在失衡节点的左子节点的左子树插入节点导致失衡(LL)

如图10,假如我们在节点5下方插入了节点2,导致节点20成为最小失衡树根节点。此时20的平衡因子是2,左子树比右子树高2,我们需要进行右旋,增加右子树高度,降低左子树高度,使平衡因子降低。

图片

图10-LL右旋

具体做法是,将20的左子节点10提升为根节点,20自身沉降为10的右子节点。此时会出现10拥有5、15、20三个子节点的情况。

10原来的右子节点15安放在哪里呢?因为20原来的左子节点10已经变成了它的父节点,那么20现在的左子节点一定是空缺的,15刚好可以放在这个位置。

所以右旋就是把左子节点提升为根节点,根节点沉降为右子节点,左子节点的右子树变为右子节点的左子树。为方便记忆,我们可以简称为“左升右降,左右变右左”。

2、在失衡节点的右子节点的右子树插入节点导致失衡(RR)

如图11,假如我们在节点60下方插入了节点70,节点40的平衡因子变成-2,该子树成为最小失衡子树。

图片

图11-RR左旋

为了使其重新平衡,需要将其左旋:根节点40向左沉降,右子节点50提升为新根节点。50的原左子节点45,变成40的新右子节点。

左旋操作可以简记为“右升左降,右左变左右”。

3、在失衡节点的左子节点的右子树插入节点导致失衡(LR)

如图12,假如我们在节点15下方插入节点12,节点20平衡因子变为2,成为最小失衡子树的根节点。此时只进行一次右旋无法使树重新平衡,需要先左旋后右旋

图片

图12-LR先左旋后右旋

先将20的左子节点10为根节点的子树左旋,节点15升为根节点(20的新左子节点),节点10向左沉降。12因为小于15大于10,它变成了10的右子节点。

然后将20为根的子树右旋,15再次升为根节点,20向右沉降。由于15没有右子节点,所以20没有得到新的左子节点。

先左旋后右旋的操作可以简记为“左子树左旋,根右旋”。

4、在失衡节点的右子节点的左子树插入节点导致失衡(RL)
        如图13,假如我们在节点45的下方插入节点48,节点40平衡因子变为-2,成为最小失衡树的根节点。只进行一次左旋无法使树重新平衡,需要先右旋后左旋

图片

图13-RL先右旋后左旋

先将40的右子节点50为根节点的子树右旋,节点45升为根节点(40的新右子节点),节点50向右沉降。48需要转变为50的左子节点。

然后将40为根的子树左旋,45再次升为根节点,40向左沉降。由于45没有左子节点,40没有获得新的右子节点。

先右旋后左旋的操作可以简记为“右子树右旋,根左旋”。


四、四种插入情况的平衡因子分析

这四种插入情况,如何让程序判断呢?

仔细观察最小失衡子树中平衡因子的变化。首先,插入节点导致失衡,必然是因为失衡树的其中一个子树树高发生了变化。从左子树中插入,必然是提高了左子树高度,那么根节点平衡因子一定为2;如果是在右子树中插入,平衡因子一定为-2。

图片

图14-失衡节点平衡因子比较

平衡因子为2的情况下,考察左子节点的平衡因子,如果是1,说明它的左子树高于右子树,高的原因是其左子树插入了节点,这种情况是LL。

如果是-1,一定是其右子树插入了节点,这种情况是LR。

图片

图15-LL和LR左子节点平衡因子比较

同样地,平衡因子为-2的情况下,就考察右子节点的平衡因子,如果是1,说明是RL情况;如果是-1,说明是RR情况。

图片

图16-RL和RR右子节点平衡因子比较

总结四种插入的情况和相应的旋转方法,如下表:

图片

图17-四种插入情况总结


五、删除节点导致失衡时的调整

在上期算法笔记中,我们讨论了二叉搜索树的删除一共分三种情况:

1、删除的节点为叶子节点

2、删除的节点只有一个子节点

3、删除的节点有两个子节点

如果严格按照上期中所述流程操作,AVL树删除节点后至少可以成为一棵二叉搜索树。接下来只需要保证是否所有节点满足平衡树的要求,即平衡因子绝对值小于2就行了。

那么,是否需要把每个节点的平衡因子遍历一遍呢?

1、删除节点后的平衡性检查

考察下图删除节点35和50的过程。删除节点35后,其父节点没有了左子树,右子树高仍为1,因此平衡因子变成了**-1**。但40所处的子树高度没有变化,因此40的父节点30平衡因子不变。

图片

图18-删除节点35和50

继续删除节点50,40的平衡因子变为了0。而因为40所处的子树高变成了1,其父节点30的平衡因子变为2,成为最小失衡节点

从上图的例子中我们可以看出,删除会使被删除节点所属的子树高度变化,进而影响到其父节点,乃至父节点的父节点平衡因子变化。但是,并不影响其兄弟节点父节点的兄弟节点。因此,我们只需要从被删除节点的位置不断向上访问父节点。当所有父节点平衡因子绝对值小于2时,整棵树就仍旧是平衡的。

而当删除导致失衡时,第一个访问到平衡因子为2或者-2的节点即是最小失衡节点。然后,我们可以判断其失衡类型来选择将其恢复平衡的旋转方式。因为我们已经分析出了LL,LR,RL,RR四种失衡类型其失衡节点和子节点平衡因子的关系,所以虽然这次不是因为插入节点导致的失衡,我们仍旧可以根据节点30平衡因子为2和节点20平衡因子为-1推断出其属于LR失衡类型,需要进行先左旋,后右旋的操作。

图片

图19-删除50后的旋转调整

仔细观察旋转过程,我们可以发现25成为了新的(子)树根。而25原来的两个子节点:左子节点22在左旋(先)过程中成为了20的右子节点,而28在右旋(后)过程中成为了30的左子节点。

2、插入节点中未出现过的失衡情况

需要注意的是,假如节点10还有子节点5和15,此时节点20的平衡因子为0。这种失衡节点平衡因子为2,其子节点却为0的情况,是我们在讨论插入节点引起的失衡时从未遇到过的,这时该怎么办呢?

简单尝试后就会发现,这种情况一次右旋即可达到平衡,因此它可以归入LL类型

图片

图20-一次右旋即可平衡

根据这个新发现,我们可以更新图17的表格:

图片

图21-失衡类型及处理

3、发生节点替代后的平衡调整

另一个需要注意的点是,删除叶子节点和只有一个子节点的节点。都可以从被删除的节点开始向上搜索父节点,访问平衡因子的情况。但是删除有两个子节点的节点,会涉及到寻找替代节点的问题。找到替代节点后,需要从替代节点的原位置开始向上搜索父节点。

如下图,删除节点40后,找到替代节点45。而替代节点换上去之后,引起了节点50的失衡。

图片

图22-删除节点40

可以根据平衡因子推断出节点50的失衡类型为RR失衡,一次左旋即可恢复平衡,如下图:

图片

图23-一次左旋即可平衡

接着检查50的父节点60、45、30的平衡因子,都是0,那么整棵树恢复平衡完毕。

综上所述,AVL删除节点的流程可以归纳为:

1、按二叉搜索树的流程删除节点

2、从被删除节点或替代节点的位置向上搜索,直到搜索到最小失衡节点或者根节点

3、判断失衡类型,执行旋转操作

4、从原最小失衡节点继续向上搜索

由于AVL树的篇幅较长,这次只介绍AVL树的理论知识。编写代码的内容,我们放在下一期中介绍。这里抛出一个问题供大家思考:为何LR和RL失衡类型无法通过一次旋转达到平衡?大家可以试着旋转操作看看,把答案打在

留言区

  • 17
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 算法9-9~9-12是关于平衡二叉树的基本操作,包括插入、删除、旋转等操作。平衡二叉树是一种特殊的二叉搜索树,它的左右子树高度差不超过1,可以保证树的高度始终在log(n)级别,从而保证了树的查找、插入、删除等操作的时间复杂度都是O(log(n))。 具体来说,算法9-9是平衡二叉树的插入操作,它首先按照二叉搜索树的规则找到要插入的位置,然后通过旋转操作来保持平衡。算法9-10是平衡二叉树的删除操作,它也是通过旋转操作来保持平衡。算法9-11和9-12是平衡二叉树的旋转操作,包括左旋、右旋、左右旋和右左旋,这些操作可以使树重新达到平衡状态。 总之,平衡二叉树的基本操作是非常重要的,它们可以保证树的高度始终在log(n)级别,从而保证了树的查找、插入、删除等操作的时间复杂度都是O(log(n)),是一种非常高效的数据结构。 ### 回答2: 平衡二叉树是一种基于二叉查找树的数据结构,其在插入和删除节点时会自动调整,以保持树的平衡性。平衡二叉树的常见有AVL树、红黑树等。本文主要介绍平衡二叉树的基本操作,包括插入、删除和查找。 9-9 插入操作 在平衡二叉树中插入一个节点的过程和在二叉查找树中插入节点的过程类似。不同的是,在插入结束后,需要检查当前节点是否失去平衡并做出相应的调整。 在插入节点时,需要记录节点的高度(从叶节点到根节点的距离)。如果当前节点为空,则将新节点插入到该节点处并将高度设置为1;否则,比较新节点的值和当前节点的值,如果新节点的值小于当前节点的值,则将新节点插入到当前节点的左子树中并更新该节点的高度;如果新节点的值大于当前节点的值,则将新节点插入到当前节点的右子树中并更新该节点的高度。 插入结束后,需要检查当前节点是否失去平衡。我们可以用该节点的左子树高度和右子树高度之差来衡量它是否平衡。如果该节点的平衡因子大于1,则需要进行旋转操作,以恢复平衡。 9-10 删除操作 在平衡二叉树中删除一个节点需要分为以下三种情况: 1. 被删除的节点为叶子节点,直接删除即可。 2. 被删除的节点有一个子节点,将该子节点代替被删除的节点即可。 3. 被删除的节点有两个子节点,需要找到它的中序遍历下一个节点(即比它大的最小节点)代替被删除的节点。如果该节点有右子树,则中序遍历下一个节点为右子树中最小的节点;如果该节点没有右子树,则中序遍历下一个节点为它的某个祖先节点。 删除结束后,需要检查当前节点是否失去平衡。如果失去平衡,则需要进行旋转操作,以恢复平衡。 9-11 查找操作 在平衡二叉树中查找一个节点的过程和在二叉查找树中查找节点的过程类似。需要从根节点开始,比较查找的值和当前节点的值。如果查找的值小于当前节点的值,则在左子树中递归查找;如果查找的值大于当前节点的值,则在右子树中递归查找;如果查找的值等于当前节点的值,则返回该节点。 9-12 平衡因子 平衡二叉树的平衡因子定义为当前节点的左子树高度和右子树高度之差。如果平衡因子的绝对值大于1,则说明该节点失去了平衡。在平衡二叉树中,每个节点的平衡因子只能为-1、0或1。如果不是这三个值,则需要进行旋转操作以恢复平衡。 ### 回答3: 平衡二叉树是一种特殊的二叉搜索树,它的左右子树高度差不超过1。由于平衡二叉树对于插入、删除、查找等操作的时间复杂度都是O(logn),因此在许多应用场景中得到了广泛的应用。平衡二叉树的基本操作包括插入、删除、查找等,以下分别介绍: 9-9 插入操作:平衡二叉树的插入操作与普通二叉搜索树相同,只是插入后需要进行平衡处理,避免出现左右子树高度差不平衡的情况。例如插入节点x,需要先查找其应当插入的位置,然后通过旋转操作将其父节点与祖父节点一起旋转,使得树重新平衡。插入操作的时间复杂度为O(logn)。 9-10 删除操作:删除操作也类似于普通二叉搜索树,需要删除节点x后通过旋转操作迭代处理其祖先节点的平衡性,保证整个树的平衡性。删除操作的时间复杂度为O(logn)。 9-11 查找操作:查找操作与普通二叉搜索树相同,只是由于平衡二叉树的高度比较平衡,因此可以保证其查找效率较高。查找操作的时间复杂度为O(logn)。 9-12 平衡操作:平衡二叉树的平衡操作主要包括旋转操作和重构操作。旋转操作通过将子树旋转到左右子树高度相等来实现平衡,分为左旋和右旋两种。重构操作通过重新构建平衡二叉树的结构来保证整个树的平衡性。平衡操作的时间复杂度为O(1)。 综上所述,平衡二叉树是一种高效的数据结构,其插入、删除、查找等基本操作时间复杂度都为O(logn),通过平衡操作可以保证整个树的平衡性。在实际应用中,平衡二叉树被广泛应用于数据库、搜索引擎、红黑树等场景中,具有重要的实用价值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值