红黑树——一个自平衡的二叉搜索树

普通的二叉搜索树在最坏的情况下,可能退化成一个链表。而又因为二叉搜索树的所有操作的性能(添加,删除,查找等),与二叉搜索树的高度有关。在最坏的情况下,二叉搜索树的高度和元素个数相同,此时二叉搜索树的效率降为了O(n)级别。

所以为了防止我们的二叉搜索树退化成一个链表,就产生了平衡二叉树平衡二叉树可以保证它的左右两个子树的高度差不会超过1。平衡二叉树有很多实现,一个经典实现就是红黑树

红黑树

在红黑树中将树中的节点划分为两种状态,分别用黑色和红色来表示。

红黑树为了保证自己能够平衡子树,所以制订以下五个规则:

1、每个节点必须有颜色,要么黑色,要么白色,没有别的颜色。
2、根节点必须是黑色
3、所有的空节点(nil节点)都认为是黑色节点。
4、红色的节点不能连续,即一个红色的节点,它的父节点和子节点不能也是红色的,
5、无论从哪一个节点起始,到它每个叶子节点的路径中,黑色节点数量必须相同。

在对红黑树进行添加、删除等操作之后,必须使红黑树符合这5个规则。
那么问题来了,在添加删除操作之后,树中节点的数量都变了,是怎么保证整个树满足上述这些规则呢?
这里涉及到3种操作,变色左旋右旋。通过这个三种操作,在增删节点之后调整树的形状结构,使它满足上述5个规则。这也是红黑树能保持平衡的原因。

变色操作我们在下文的添加、删除节点的实际操作中,再进行在描述。

先来说一下左右旋。

左旋

左旋是指以某节点为支点,进行逆时针旋转。如下图,是以2为支点进行的左旋:
《红黑树——一个自平衡的二叉搜索树》 图1.png

文字描述一下就是,2的右孩子节点4,变为了2的父节点,2由父节点变为4的左孩子。同时,4原来的左孩子变为2的右孩子。

右旋

右旋与左旋相反,即以某节点为支点进行顺时针旋转。同样,我们看下图,是以5为支点进行的右旋:

《红黑树——一个自平衡的二叉搜索树》 图2.png

文字描述同样反过来,5的左孩子节点3,变为5的父节点,5由父节点变为3的右孩子。3原来的右孩子变为5的左孩子。

插入新节点

首先是在树中找到新节点正确的位置,寻找位置的过程与普通的二叉搜索树相同,只是将新插入的节点默认为红色节点。为什么默认为红色?因为如果你将新节点默认为黑色,则插入后肯定会打破原本符合规则的红黑树(上述第5条规则)。但是,如果你将新节点定为红色,则有可能不用任何操作就符合红黑树规则,如下图,当新插入的红色节点,它的父亲节点为黑色时候,此时已经满足红黑树规则了。所以用红色比黑色好。

《红黑树——一个自平衡的二叉搜索树》 图3.png

如果很不巧,新插入的节点的父亲节点也为红色,因为红色节点不能连续,所以我们需要调整红黑树的结构使其满足规则。在调整的过程中我们会遇到3种需要处理的情况,我们来一一进行说明。

情况1:

《红黑树——一个自平衡的二叉搜索树》 图4.png

插入新节点40,此时它的父节点为红色,并且它的叔叔节点(即51)也为红色。此时我们需要进行变色操作。将该节点的父亲节点、叔叔节点都变为黑色,祖父节点变为红色

《红黑树——一个自平衡的二叉搜索树》 图5.png

此时上图已经满足红黑树的规则。但有的时候我们经过了变色操作后,仍不满足红黑树的规则,会遇到下面的情况。
情况2:

《红黑树——一个自平衡的二叉搜索树》 图9.png

如图,我们插入新的节点53,在按情况1的操作变色后,变成了这样:

《红黑树——一个自平衡的二叉搜索树》 图10.png

此时,49与44为两个连续的红色,显然不符合规则。此时的操作为:我们将49看做当前节点,将当前节点的父节点44变为黑色,祖父节点35置为红色,以祖父节点为支点进行
左旋

《红黑树——一个自平衡的二叉搜索树》 图11.png

此时情况2就处理完成了。

最后我们说一下情况3的情景,如下图:

《红黑树——一个自平衡的二叉搜索树》 图6.png

我们向树中插入新节点37,在按情况1的操作变色后,变成了这样:

情况3:

《红黑树——一个自平衡的二叉搜索树》 图7.png

此时,49与44为两个连续的红色,显然不符合规则。而我们只需以49为支点,进行一次右旋,就变成了情况2。如下图。

《红黑树——一个自平衡的二叉搜索树》 图8.png

再按情况2进行一次操作就符合规则了。

3种情况我们说明完了,但是你可能还会有这样的疑问,什么时候进行左旋,什么时候进行右旋;什么时候以父节点为支点旋转,什么时候又以祖父节点为支点旋转?

那么我们可以总结一下,当遇到连续的红色节点应该怎么办:当前节点我们叫它X,如果X相对于父节点的左右位置和父节点相对于祖父节点的左右位置相同,此时,就以祖父节点为支点,进行反向旋转。例如:X为父节点的左孩子,X的父节点同样也是其祖父节点的左孩子,此时以祖父节点为支点进行右旋;
如果X相对于父节点的左右位置和父节点相对于祖父节点的左右位置不同,则以X的父节点为支点,进行旋转,旋转方向与X相对于父节点左右位置相反。例如:X为其父节点的左孩子,X的父节点为祖父节点的右孩子,此时以X的父节点为支点进行一次右旋。

删除节点

在红黑树中删除节点,肯定要涉及到要删的这个节点是红色的还是黑色的。删除红色比较简单,我们先说一下删除红色节点。

删除节点要考虑这个节点所处的位置,所以我们罗列一下红色的节点所有可能的位置情况。

  • 它是一个叶子节点。
  • 它既有左子树也有右子树。

你可能会发现为什么少了一种情况?它不能只有左子树或者只有右子树吗?我们可以看下图:

《红黑树——一个自平衡的二叉搜索树》 图12.png

很明显,这四种情况都不符合红黑树的规则,所以根本不会出现这种情况。

而对于既有左子树也有右子树的情况。我们可以先和普通的二叉搜索树的删除操作一样,将它与前驱或者后继交换一下。它就又变成第一种情况——成为了一个叶子节点。所以我们只需考虑当它是叶子节点的情况。

《红黑树——一个自平衡的二叉搜索树》 图13.png

很简单,直接删除红色叶子节点。

接下来我们看一下当要删除的节点是黑色的时候应该怎么办。
同样我们列一下节点位置可能的情况:

  • 它是一个叶子节点。
  • 它只有左子树,或只有右子树。
  • 它既有左子树也有右子树。

第三种情况和删除红色节点时的处理方法一样,可以转换成第一种或第二种情况,所以我们只关心前两种情况。

当要删除的黑色节点只有一个子树时:

《红黑树——一个自平衡的二叉搜索树》 图14.png

它的左孩子或者右孩子一定是红色的,因为如果是黑色的就不符合红黑树的规则了。

操作方法为:我们只需要将它的子节点变黑,然后代替它的位置就完成了。
《红黑树——一个自平衡的二叉搜索树》 图15.png

最后我们看一下最难处理的一种情况。

要删除的黑色节点是叶子节点时:
情况1:待删除黑色节点20,它的兄弟节点为红色。

《红黑树——一个自平衡的二叉搜索树》 图16.png

此时的操作为,将兄弟节点和父节点颜色交换,即父亲变红,兄弟变黑。然后以父节点为支点进行
左旋。(旋转方向同样是与待删除节点的左右位置相同)

《红黑树——一个自平衡的二叉搜索树》 图17.png

此时会变成了下述的情况4,再按情况4进行操作就可以了。

情况2:待删除黑色节点20,它的兄弟节点为黑色,并且它拥有红色的远侄子节点,近侄子节点有没有都可以。(侄子节点即兄弟节点的子节点,远侄子节点就是,当前节点如果是其父节点的左孩子,那么它的远侄子节点就是兄弟节点的右孩子,近侄子同理)

《红黑树——一个自平衡的二叉搜索树》 图18.png

操作方法为:将远侄子节点变黑,兄弟节点与父亲节点互换颜色,最后以父节点为支点进行左旋。(为什么是左旋?因为待删除的20是左孩子,我们要将左子树长度拉长,将它沉下来,使它变成多余的节点好删除它,如果它是右孩子,则进行右旋)
操作后如下图就完成了。

《红黑树——一个自平衡的二叉搜索树》 图19.png

情况3:待删除黑色节点20,它的兄弟节点为黑色,但它没有红色的远侄子节点(即nil点,记住,nil点算黑色),只有红色的近侄子节点。

《红黑树——一个自平衡的二叉搜索树》 图20.png

操作方法为:将兄弟节点与近侄子节点交换颜色,再以兄弟节点进行右旋。(旋转方向很好记,因为此处旋转的目的是为了创造远侄子,近侄子节点是左节点,所以就只能右旋了,如果近侄子是右节点则进行左旋。)

操作后如下图:

《红黑树——一个自平衡的二叉搜索树》 图21.png

此时有了红色的远侄子,就满足了情况2,再按情况2进行一次操作就完成了。

情况4:待删除黑色节点20,它的兄弟节点为黑色,远侄子、近侄子节点都没有。(即两个nil节点,nil节点算黑色)

《红黑树——一个自平衡的二叉搜索树》 图24.png

操作方法为:将兄弟节点变为红色,同时最关键的是,将当前节点20的父节点50,看做当前节点,继续递归的进行这四种情况的判断,直到当前节点为红色,或者当前节点是根节点才停止。
最后将当前节点变为黑色!!

我们将上图红黑树按流程演示一下:
第一步按情况4操作,将55变红。并将父节点50看做当前节点,继续操作。

《红黑树——一个自平衡的二叉搜索树》 图25.png

此时,当前节点50为黑色,兄弟节点68也为黑色,且它的两个侄子65和70都为黑色,所以仍符合情况4。将68变红,并将父节点61看做当前节点,继续操作。
《红黑树——一个自平衡的二叉搜索树》 图26.png

此时当前节点61为红色,满足停止递归条件,将61变为黑色,停止。整个操作完成。
《红黑树——一个自平衡的二叉搜索树》 图27.png

此时有关红黑树的知识就说完了。
以上所有内容都为自己查阅资料学习理解之后手敲的。尽量得采用通俗易懂的描述和解释让读者更明白。27张图都是自己亲自画的,花费了四天才写完,如果觉得写的还可以,麻烦点亮喜欢支持一下,如果还是不懂,可以下方留言QQ等联系方式,我亲自告诉你。

    原文作者:皮蛋solo粥
    原文地址: https://www.jianshu.com/p/bad1e3f26466
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞