• 30张图带你彻底理解红黑树(转)


    作者:安卓大叔
    链接:https://www.jianshu.com/p/e136ec79235c
    来源:简书

    正文

    红黑树也是二叉查找树,我们知道,二叉查找树这一数据结构并不难,而红黑树之所以难是难在它是自平衡的二叉查找树,在进行插入和删除等可能会破坏树的平衡的操作时,需要重新自处理达到平衡状态。现在在脑海想下怎么实现?是不是太多情景需要考虑了?啧啧,先别急,通过本文的学习后,你会觉得,其实也不过如此而已。好吧,我们先来看下红黑树的定义和一些基本性质。

    红黑树的定义和性质

    红黑树是一种含有红黑结点并能自平衡的二叉查找树。它必须满足下面性质:

    • 性质1:每个节点要么是黑色,要么是红色。
    • 性质2:根节点是黑色。
    • 性质3:每个叶子节点(NIL)是黑色。
    • 性质4:每个红色结点的两个子结点一定都是黑色。
    • 性质5:任意一结点到每个叶子结点的路径都包含数量相同的黑结点

    从性质5又可以推出:

    • 性质5.1:如果一个结点存在黑子结点,那么该结点肯定有两个子结点

    图1就是一颗简单的红黑树。其中Nil为叶子结点,并且它是黑色的。(值得提醒注意的是,在Java中,叶子结点是为null的结点。)
    一颗简单的红黑树

    红黑树并不是一个完美平衡二叉查找树,从图1可以看到,根结点P的左子树显然比右子树高,但左子树和右子树的黑结点的层数是相等的,也即任意一个结点到到每个叶子结点的路径都包含数量相同的黑结点(性质5)。所以我们叫红黑树这种平衡为黑色完美平衡

    介绍到此,为了后面讲解不至于混淆,我们还需要来约定下红黑树一些结点的叫法,如图2所示。

    节点叫法约章

    我们把正在处理(遍历)的结点叫做当前结点,如图2中的D,它的父亲叫做父结点,它的父亲的另外一个子结点叫做兄弟结点,父亲的父亲叫做祖父结点。

    前面讲到红黑树能自平衡,它靠的是什么?三种操作:左旋、右旋和变色。

    • 左旋:以某个结点作为支点(旋转结点),其右子结点变为旋转结点的父结点,右子结点的左子结点变为旋转结点的右子结点,左子结点保持不变。如图3。
    • 右旋:以某个结点作为支点(旋转结点),其左子结点变为旋转结点的父结点,左子结点的右子结点变为旋转结点的左子结点,右子结点保持不变。如图4。
    • 变色:结点的颜色由红变黑或由黑变红。
      左旋
      右旋

    上面所说的旋转结点也即旋转的支点,图4和图5中的P结点。
    我们先忽略颜色,可以看到旋转操作不会影响旋转结点的父结点,父结点以上的结构还是保持不变的。
    左旋只影响旋转结点和其右子树的结构,把右子树的结点往左子树挪了。
    右旋只影响旋转结点和其左子树的结构,把左子树的结点往右子树挪了。

    所以旋转操作是局部的。另外可以看出旋转能保持红黑树平衡的一些端详了:当一边子树的结点少了,那么向另外一边子树“借”一些结点;当一边子树的结点多了,那么向另外一边子树“租”一些结点。

    但要保持红黑树的性质,结点不能乱挪,还得靠变色了。怎么变?具体情景又不同变法,后面会具体讲到,现在只需要记住红黑树总是通过旋转和变色达到自平衡。

    balabala了这么多,相信你对红黑树有一定印象了,那么现在来考考你:

    思考题1:黑结点可以同时包含一个红子结点和一个黑子结点吗? (答案见文末)

    接下来先讲解红黑树的查找热热身。

    红黑树查找

    因为红黑树是一颗二叉平衡树,并且查找不会破坏树的平衡,所以查找跟二叉平衡树的查找无异:

    1、从根结点开始查找,把根结点设置为当前结点;
    2、若当前结点为空,返回null;
    3、若当前结点不为空,用当前结点的key跟查找key作比较;
    4、若当前结点key等于查找key,那么该key就是查找目标,返回当前结点;
    5、若当前结点key大于查找key,把当前结点的左子结点设置为当前结点,重复步骤2;
    6、若当前结点key小于查找key,把当前结点的右子结点设置为当前结点,重复步骤2;
    如图5所示。
    查找流程

    非常简单,但简单不代表它效率不好。正由于红黑树总保持黑色完美平衡,所以它的查找最坏时间复杂度为(O(log_2 N)),也即整颗树刚好红黑相隔的时候。能有这么好的查找效率得益于红黑树自平衡的特性,而这背后的付出,红黑树的插入操作功不可没~

    红黑树插入

    插入操作包括两部分工作:一查找插入的位置;二插入后自平衡。查找插入的父结点很简单,跟查找操作区别不大:

    1、从根结点开始查找;
    2、若根结点为空,那么插入结点作为根结点,结束。
    3、若根结点不为空,那么把根结点作为当前结点;
    4、若当前结点为null,返回当前结点的父结点,结束。
    5、若当前结点key等于查找key,那么该key所在结点就是插入结点,更新结点的值,结束。
    6、若当前结点key大于查找key,把当前结点的左子结点设置为当前结点,重复步骤4;
    7、若当前结点key小于查找key,把当前结点的右子结点设置为当前结点,重复步骤4;

    如图6所示。

    ok,插入位置已经找到,把插入结点放到正确的位置就可以啦,但插入结点是应该是什么颜色呢?答案是红色。理由很简单,红色在父结点(如果存在)为黑色结点时,红黑树的黑色平衡没被破坏,不需要做自平衡操作。但如果插入结点是黑色,那么插入位置所在的子树黑色结点总是多1,必须做自平衡。

    所有插入情景如图7所示。

    嗯,插入情景很多呢,8种插入情景!但情景1、2和3的处理很简单,而情景4.2和情景4.3只是方向反转而已,懂得了一种情景就能推出另外一种情景,所以总体来看,并不复杂,后续我们将一个一个情景来看,把它彻底搞懂。

    另外,根据二叉树的性质,除了情景2,所有插入操作都是在叶子结点进行的。这点应该不难理解,因为查找插入位置时,我们就是在找子结点为空的父结点的。

    在开始每个情景的讲解前,我们还是先来约定下,如图8所示。

    图8的字母并不代表结点Key的大小。I表示插入结点,P表示插入结点的父结点,S表示插入结点的叔叔结点,PP表示插入结点的祖父结点.

    好了,下面让我们一个一个来分析每个插入的情景以其处理。

    插入情景1:红黑树为空树

    最简单的一种情景,直接把插入结点作为根结点就行,但注意,根据红黑树性质2:根节点是黑色。还需要把插入结点设为黑色。
    处理:把插入结点作为根结点,并把结点设置为黑色。

    插入情景2:插入结点的Key已存在

    插入结点的Key已存在,既然红黑树总保持平衡,在插入前红黑树已经是平衡的,那么把插入结点设置为将要替代结点的颜色,再把结点的值更新就完成插入。
    **处理:

    • 把I设置为当前节点的颜色
    • 更新当前节点的值为插入节点的值

    插入情景3:插入结点的父结点为黑结点

    由于插入的结点是红色的,当插入结点的父结点黑色时,并不会影响红黑树的平衡,直接插入即可,无需做自平衡。
    处理:直接插入。

    插入情景4:插入结点的父结点为红结点

    再次回想下红黑树的性质2:根结点是黑色。如果插入的父结点为红结点,那么该父结点不可能为根结点,所以插入结点总是存在祖父结点。这点很重要,因为后续的旋转操作肯定需要祖父结点的参与。
    情景4又分为很多子情景,下面将进入重点部分,各位看官请留神了。

    插入情景4.1:叔叔结点存在并且为红结点

    从红黑树性质4可以,祖父结点肯定为黑结点,因为不可以同时存在两个相连的红结点。那么此时该插入子树的红黑层数的情况是:黑红红。显然最简单的处理方式是把其改为:红黑红。如图9和图10所示。
    处理:

    • 将P和S设置为黑色
    • 将PP设置为红色
    • 把PP设置为当前插入结点

    可以看到,我们把PP结点设为红色了,如果PP的父结点是黑色,那么无需再做任何处理;但如果PP的父结点是红色,根据性质4,此时红黑树已不平衡了,所以还需要把PP当作新的插入结点,继续做插入操作自平衡处理,直到平衡为止。

    试想下PP刚好为根结点时,那么根据性质2,我们必须把PP重新设为黑色,那么树的红黑结构变为:黑黑红。换句话说,从根结点到叶子结点的路径中,黑色结点增加了。这也是唯一一种会增加红黑树黑色结点层数的插入情景。

    我们还可以总结出另外一个经验:红黑树的生长是自底向上的。这点不同于普通的二叉查找树,普通的二叉查找树的生长是自顶向下的。

    插入情景4.2:叔叔结点不存在或为黑结点,并且插入结点的父亲结点是祖父结点的左子结点

    单纯从插入前来看,也即不算情景4.1自底向上处理时的情况,叔叔结点非红即为叶子结点(Nil)。因为如果叔叔结点为黑结点,而父结点为红结点,那么叔叔结点所在的子树的黑色结点就比父结点所在子树的多了,这不满足红黑树的性质5。后续情景同样如此,不再多做说明了。

    前文说了,需要旋转操作时,肯定一边子树的结点多了或少了,需要租或借给另一边。插入显然是多的情况,那么把多的结点租给另一边子树就可以了。

    插入情景4.2.1:插入结点是其父结点的左子结点

    处理:
    将P设为黑色
    将PP设为红色
    对PP进行右旋

    由图11可得,左边两个红结点,右边不存在,那么一边一个刚刚好,并且因为为红色,肯定不会破坏树的平衡。

    咦,可以把P设为红色,I和PP设为黑色吗?答案是可以!看过《算法:第4版》的同学可能知道,书中讲解的就是把P设为红色,I和PP设为黑色。但把P设为红色,显然又会出现情景4.1的情况,需要自底向上处理,做多了无谓的操作,既然能自己消化就不要麻烦祖辈们啦~

    插入情景4.2.2:插入结点是其父结点的右子结点

    这种情景显然可以转换为情景4.2.1,如图12所示,不做过多说明了。
    处理:

    • 对P进行左旋
    • 把P设置为插入结点,得到情景4.2.1
    • 进行情景4.2.1的处理

    插入情景4.3:叔叔结点不存在或为黑结点,并且插入结点的父亲结点是祖父结点的右子结点

    该情景对应情景4.2,只是方向反转,不做过多说明了,直接看图。

    插入情景4.3.1:插入结点是其父结点的右子结点

    处理:

    • 将P设为黑色
    • 将PP设为红色
    • 对PP进行左旋

    插入情景4.3.2:插入结点是其父结点的左子结点

    处理:

    • 对P进行右旋
    • 把P设置为插入结点,得到情景4.3.1
    • 进行情景4.3.1的处理

    好了,讲完插入的所有情景了。可能又同学会想:上面的情景举例的都是第一次插入而不包含自底向上处理的情况,那么上面所说的情景都适合自底向上的情况吗?答案是肯定的。理由很简单,但每棵子树都能自平衡,那么整棵树最终总是平衡的。好吧,在出个习题,请大家拿出笔和纸画下试试(请务必动手画下,加深印象)

    习题1:请画出图15的插入自平衡处理过程。(答案见文末)

    红黑树删除

    红黑树插入已经够复杂了,但删除更复杂,也是红黑树最复杂的操作了。但稳住,胜利的曙光就在前面了!

    红黑树的删除操作也包括两部分工作:一查找目标结点;二删除后自平衡。查找目标结点显然可以复用查找操作,当不存在目标结点时,忽略本次操作;当存在目标结点时,删除后就得做自平衡处理了。删除了结点后我们还需要找结点来替代删除结点的位置,不然子树跟父辈结点断开了,除非删除结点刚好没子结点,那么就不需要替代。

    二叉树删除结点找替代结点有3种情情景:

    • 情景1:若删除结点无子结点,直接删除
    • 情景2:若删除结点只有一个子结点,用子结点替换删除结点
    • 情景3:若删除结点有两个子结点,用后继结点(大于删除结点的最小结点)替换删除结点

    补充说明下,情景3的后继结点是大于删除结点的最小结点,也是删除结点的右子树种最左结点。那么可以拿前继结点(删除结点的左子树最右结点)替代吗?可以的。但习惯上大多都是拿后继结点来替代,后文的讲解也是用后继结点来替代。另外告诉大家一种找前继和后继结点的直观的方法(不知为何没人提过,大家都知道?):把二叉树所有结点投射在X轴上,所有结点都是从左到右排好序的,所有目标结点的前后结点就是对应前继和后继结点。如图16所示。

    接下来,讲一个重要的思路:删除结点被替代后,在不考虑结点的键值的情况下,对于树来说,可以认为删除的是替代结点!话很苍白,我们看图17。在不看键值对的情况下,图17的红黑树最终结果是删除了Q所在位置的结点!这种思路非常重要,大大简化了后文讲解红黑树删除的情景!

    基于此,上面所说的3种二叉树的删除情景可以相互转换并且最终都是转换为情景1!

    • 情景2:删除结点用其唯一的子结点替换,子结点替换为删除结点后,可以认为删除的是子结点,若子结点又有两个子结点,那么相当于转换为情景3,一直自顶向下转换,总是能转换为情景1。(对于红黑树来说,根据性质5.1,只存在一个子结点的结点肯定在树末了)
    • 情景3:删除结点用后继结点(肯定不存在左结点),如果后继结点有右子结点,那么相当于转换为情景2,否则转为为情景1。

    二叉树删除结点情景关系图如图18所示。

    综上所述,删除操作删除的结点可以看作删除替代结点,而替代结点最后总是在树末。有了这结论,我们讨论的删除红黑树的情景就少了很多,因为我们只考虑删除树末结点的情景了。

    同样的,我们也是先来总体看下删除操作的所有情景,如图19所示。

    哈哈,是的,即使简化了还是有9种情景!但跟插入操作一样,存在左右对称的情景,只是方向变了,没有本质区别。同样的,我们还是来约定下,如图20所示。

  • 相关阅读:
    [Luogu P2563]质数和分解
    数据处理、降维--线性判别分析(linear discriminant analysis LDA)
    endnote导入参考文献及国标(Chinese standard)
    朴素贝叶斯分类器
    R语言--apply家族
    R语言--解决4*x+y之奇技淫巧、重复数字去重
    R语言中级--自定义方程
    R语言--一整套数据处理流程及简单的控制流
    R语言图形初阶
    R基础、进阶-矩阵数据框的操作
  • 原文地址:https://www.cnblogs.com/liuzhidao/p/14272157.html
Copyright © 2020-2023  润新知