-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
95 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
--- | ||
layout: post | ||
title: '“它刚才还是平衡的”' | ||
--- | ||
之前一直在吐槽学校的数据结构与算法课太水了,结果马上就被 AVL 树打了一顿。 | ||
|
||
我中学就没怎么听树形数据结构,回来就只能浪费几到十几个小时翻资料…… | ||
|
||
# 节点高度变化的规律 | ||
|
||
如果一个节点的高度变化了一个单位,就可能会让父节点高度同样变化。(废话) | ||
|
||
如果父节点的高度变化了,就关于父节点也考虑一下高度的变化。如果父节点的高度没变,就再也没有别的节点会改变高度了。(废话) | ||
|
||
父节点高度不变有两种情况,一种是两棵子树始终平衡,并且较高的那棵高度没变;另一种是两棵子树不再平衡,父节点高度先是增加,不过我们用算法让两棵子树的高度“中和”了一下,让父节点的高度变回来了。 | ||
|
||
节点高度是从下到上开始考虑的,第一个改变高度的节点显然不会影响它的子节点。一个节点只有自己的高度改变了,才有可能改变父节点的高度(废话),所以所有改变高度的节点可以连成唯一的一条路径。因为重新平衡的算法一定会让不平衡点的高度恢复到原来的状态,而不平衡是由节点高度改变产生的,所以重新平衡只有可能出现在路径的末端。所以,一步插入、删除操作最多只需要对树进行一次重新平衡。 | ||
|
||
# 各种旋转 | ||
|
||
如果一棵树之前是平衡的,而现在不平衡了,既然一个插入/删除操作最多只会让节点的高度改变一个单位,说明这棵树的两棵子树原本就不一样高。有一棵子树比另一棵高一个单位,经历一步操作之后本来高的那棵又升高了一个单位,或者本来矮的那棵又降低了一个单位,树就不平衡了。 | ||
|
||
随便找个右子树原本就高一点,后面又升高了而导致不平衡的情况。圆圈表示节点,圆圈内部是节点的名字,右边表示节点的高度。`A`是需要重新平衡的节点,`B`、`D`和`E`的子树省略了,它们在这里不重要。这是插入新节点之前: | ||
|
||
![1]({{site.url}}/res/avl/tree-1.webp) | ||
|
||
插入新节点之后,`C`又升高了一个单位。考虑到前面节点高度变化的规律,这里只有`A`两边是不平衡的,其他的都平衡。 | ||
|
||
![2]({{site.url}}/res/avl/tree-2.webp) | ||
|
||
现在来考虑一下`D`和`E`的高度。`C`原本的高度是`n+1`,说明`D`和`E`里至少有一个高度为`n`,同时最低的那棵高度不能低于`n-1`。`C`的子节点要么高度都是`n`,要么有一个高度为`n`而另一个高度为`n-1`。不过第二种情况不可能发生,因为如果`C`的子节点高度不同,假设较矮的那棵子树升高了一个单位,`C`的高度就不会改变;如果较高的那棵升高了,`C`就不平衡了。这说明`D`和`E`原本是一样高的,只可能是`n`。 | ||
|
||
重新画一下插入新节点之前的图: | ||
|
||
![3]({{site.url}}/res/avl/tree-3.webp) | ||
|
||
这时考虑插入节点后`D`和`E`的高度,就有两种情况了: | ||
|
||
![4]({{site.url}}/res/avl/tree-4-7-9.webp) | ||
|
||
![5]({{site.url}}/res/avl/tree-5.webp) | ||
|
||
第二种情况处理起来比较简单。如果能改变这树的结构,让左子树升高一个单位,右子树降低一个单位,两边的树高变为`n+1`,树也就重新平衡了。这样处理还有一个好处,就是操作之后最高的节点高度为`n+2`,和插入新节点之前的一样,不会引起它父节点高度的变化,也就不用考虑后续平衡的问题了。 | ||
|
||
现在想让`A`成为左子树的一部分(为了让左子树升高一个单位),`C`不再是右子树的一部分(为了让右子树降低一个单位),就让`C`成为新的最高节点(暂且让我这么说吧,毕竟管它叫“根节点”不太好),让`A`变成`C`的左子树。为了在左边腾出空间,先让`D`变成游离的,之后往`A`变成子节点后空出的右边插就可以了。有点像让`A`和`C`绕着下方的一个点逆时针旋转一下,所以这个算法也叫“旋转”。 | ||
|
||
![6]({{site.url}}/res/avl/tree-6.webp) | ||
|
||
至于这样是叫左旋还是右旋,不同语言的书上不太一样。我的书上是 right-right,听起来有点反直觉,实际上是说不平衡是由`A`的右子树的右子节点升高引发的。 | ||
|
||
总结来说就是将`C`的左子节点设为`A`,然后将`A`的右子节点设为`D`。`B < A < D < C < E`,重新平衡之后的树也是符合二叉搜索树定义的。 | ||
|
||
现在我们已经能处理第二种不平衡的情况了,再来看下第一种。这个不平衡是由右子树的左子节点升高引发的: | ||
|
||
![7]({{site.url}}/res/avl/tree-4-7-9.webp) | ||
|
||
要是还按第二种情况的方法操作(right-right 旋转),得到的树是这样: | ||
|
||
![8]({{site.url}}/res/avl/tree-8.webp) | ||
|
||
因为`D`的高度比`B`多一个单位,`A`的高度也要增加,树仍然是不平衡的。要是能把它变成 right-right 的情况,之后直接套用刚才的步骤就方便得多了。 | ||
|
||
重新观察一下 right-left 的树。要记得 right-left 的意思是,不平衡是由右子树的左节点升高而引发的: | ||
|
||
![9]({{site.url}}/res/avl/tree-4-7-9.webp) | ||
|
||
这次先把右子树旋转一下,让右子树的右节点`E`比左节点`D`高就是了。要旋转右子树就需要考虑`D`的子节点的情况,把`D`的两个子节点标出来: | ||
|
||
![10]({{site.url}}/res/avl/tree-10.webp) | ||
|
||
先让`A`的右节点`C`left-left 旋转一下: | ||
|
||
![11]({{site.url}}/res/avl/tree-11.webp) | ||
|
||
再让`A`right-right 转一下就好了: | ||
|
||
![12]({{site.url}}/res/avl/tree-12.webp) | ||
|
||
至于`F`和`G`的高度,用类似刚才分析`C`子节点高度的方法分析一下,`D`的子节点也是一个`n`一个`n-1`。我们没动`B`和`E`的子节点,所以它俩仍然是`n`。 | ||
|
||
算下无论`F`和`G`哪个是`n`哪个是`n-1`,树都是平衡的: | ||
|
||
![13]({{site.url}}/res/avl/tree-13.webp) | ||
|
||
(若是你用`F = n-1`和`G = n`的情况考虑一下这树第一步旋转的情况,会发现旋转后会右两处不平衡,不过第二次旋转之后就都平衡了) | ||
|
||
复习一下,右子树的右节点升高引发的不平衡需要 right-right 旋转。反过来,左子树的左节点升高若是搞不清楚方向,知道这里所说的 right-right 旋转会让目标的地位被它的右节点取代就是了。 | ||
|
||
右子树的左节点升高需要 right-left。一个 right-left 操作,相当于先对右子树根进行 left-left 旋转,再对最高节点进行 right-right 旋转。 | ||
|
||
上面这些旋转解决的都是插入(高度升高)引发的不平衡。删除(高度降低)的话实际也差不多,感兴趣的同学可以自己试一下。 | ||
|
||
# 4.0 学分的数据结构与算法 | ||
|
||
我现在又希望课能水一点了。水一点好,水一点学着会轻松很多…… |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.