数据结构——红黑树简谈

一、基本概念

R-B Tree,全称是Red-Black Tree,又称为“红黑树”,它一种特殊的二叉查找树。红黑树的每个节点上都有存储位表示节点的颜色,可以是红(Red)或黑(Black)。

红黑树的特性:
(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
(4)如果一个节点是红色的,则它的子节点必须是黑色的。
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

注意:因为(1)(4)(5)的保证,没有一条路径会比其他路径长出两倍,红黑树是相对接近平衡的二叉树。

  • 性质:如果一个结点存在黑子结点,那么该结点肯定有两个子结点
  • 数据结构——红黑树简谈_第1张图片

数据结构——红黑树简谈_第2张图片

 (1)红黑树的应用

        红黑树的应用比较广泛,主要是用它来存储有序的数据,它的时间复杂度是O(lgn),效率非常之高。
        例如,Java集合中的TreeSet和TreeMap,C++ STL中的set、map,以及Linux虚拟内存的管理,都是通过红黑树去实现的。

(2)时间复杂度:

红黑树的时间复杂度为: O(logn)

一棵含有n个节点的红(2)时间复杂度黑树的高度至多为2log(n+1).

(3)旋转:

左旋:对当前节点左旋,使得当前节点成为其右子树的左子树

右旋:对当前节点右旋,使得当前节点成为其左子树的右子树

左旋示意图:

数据结构——红黑树简谈_第3张图片

数据结构——红黑树简谈_第4张图片

右旋示意图:

数据结构——红黑树简谈_第5张图片

数据结构——红黑树简谈_第6张图片

二、查找:一般搜索二叉树的查找

  • 从根结点开始查找,把根结点设置为当前结点;
  • 若当前结点为空,返回null;
  • 若当前结点不为空,用当前结点的key跟查找key作比较;
  • 若当前结点key等于查找key,那么该key就是查找目标,返回当前结点;
  • 若当前结点key大于查找key,把当前结点的左子结点设置为当前结点,重复步骤2;
  • 若当前结点key小于查找key,把当前结点的右子结点设置为当前结点,重复步骤2;

三、插入

        将一个节点插入到红黑树中:

                首先,将红黑树当作一颗二叉查找树,将节点插入;

                然后,将节点着色为红色;

                最后,通过旋转和重新着色等方法来修正该树,使之重新成为一颗红黑树。

第一步: 将红黑树当作一颗二叉查找树,将节点插入。
       红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。也就意味着,树的键值仍然是有序的。此外,无论是左旋还是右旋,若旋转之前这棵树是二叉查找树,旋转之后它一定还是二叉查找树。这也就意味着,任何的旋转和重新着色操作,都不会改变它仍然是一颗二叉查找树的事实。

第二步:将插入的节点着色为"红色"。
       为什么着色成红色,而不是黑色呢?为什么呢?在回答之前,我们需要重新温习一下红黑树的特性:
        (1) 每个节点或者是黑色,或者是红色。
        (2) 根节点是黑色。
        (3) 每个叶子节点是黑色。 [注意:这里叶子节点,是指为空的叶子节点!]
        (4) 如果一个节点是红色的,则它的子节点必须是黑色的。
        (5) 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
       将插入的节点着色为红色,不会违背"特性(5)"!少违背一条特性,就意味着我们需要处理的情况越少。接下来,就要努力的让这棵树满足其它性质即可;满足了的话,它就又是一颗红黑树了。o(∩∩)o...哈哈

第三步: 通过一系列的旋转或着色等操作,使之重新成为一颗红黑树。

        第二步中,将插入节点着色为"红色"之后,不会违背"特性(5)"。那它到底会违背哪些特性呢?
               对于"特性(1)",显然不会违背了。因为我们已经将它涂成红色了。
               对于"特性(2)",只有在一种情况下会违背特性2,在当前节点插入前,红黑树是一棵空树,那么当前插入的节点即为根节点,那么将当前节点染成黑色即可。其他情况显然也不会违背。在第一步中,我们是将红黑树当作二叉查找树,然后执行的插入操作。而根据二叉查找数的特点,插入操作不会改变根节点。所以,根节点仍然是黑色。   
                对于"特性(3)",显然不会违背了。这里的叶子节点是指的空叶子节点,插入非空节点并不会对它们造成影响。
               对于"特性(4)",是有可能违背的!


       那接下来,想办法使之"满足特性(4)",就可以将树重新构造成红黑树了。

插入情况:

数据结构——红黑树简谈_第7张图片

数据结构——红黑树简谈_第8张图片

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

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

        处理:把插入结点作为根结点,并把结点设置为黑色。

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

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

处理:

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

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

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

处理:直接插入。

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

        再次回想下红黑树的性质2:根结点是黑色。如果插入的父结点为红结点,那么该父结点不可能为根结点,所以插入结点总是存在祖父结点。这点很重要,因为后续的旋转操作肯定需要祖父结点的参与。

情景4又分为很多子情景,下面将进入重点部分,各位看官请留神了。

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

        处理:

                将P和S设置为黑色

                将PP设置为红色

                把PP设置为当前插入结点

数据结构——红黑树简谈_第9张图片

数据结构——红黑树简谈_第10张图片

                可以看到,我们把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设为红色,显然又会继续自底向上调整。但是把P设为红色,I和PP设为黑色,可以把P设为当前节点,然后适合递归代码的编写,因为每次插入的节点是一个红色节点。

                插入情景4.2.2:插入结点是其父结点的右子结点
                这种情景显然可以转换为情景4.2.1,如图12所示,不做过多说明了。

                处理:

                        对P进行左旋

                        把P设置为插入结点,得到情景4.2.1

                        进行情景4.2.1的处理

数据结构——红黑树简谈_第12张图片

        插入情景4.3:叔叔结点不存在或为黑结点,并且插入结点的父亲结点是祖父结点的右子结点
该情景对应情景4.2,只是方向反转,不做过多说明了,直接看图。

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

                        将P设为黑色

                        将PP设为红色

                        对PP进行左旋

数据结构——红黑树简谈_第13张图片

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

                        对P进行右旋

                        把P设置为插入结点,得到情景4.3.1

                        进行情景4.3.1的处理

数据结构——红黑树简谈_第14张图片

四、删除

        红黑树的删除操作也包括两部分工作:

                一查找目标结点;

                而删除后自平衡。

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

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

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

        这里的替换,是指的值替换。        

        对于情景3,我们用前驱(左子树最右节点)或者后继(右子树最左节点)来替换,转换为删除前驱或者后继,则前驱节点或者后继节点必至多只有一个子节点。

然后将前驱或者后继的值复制到要删除的节点中,最后再将前驱或后继删除。

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

数据结构——红黑树简谈_第15张图片

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

数据结构——红黑树简谈_第16张图片

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

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

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

数据结构——红黑树简谈_第17张图片

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

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

数据结构——红黑树简谈_第18张图片

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

数据结构——红黑树简谈_第19张图片

        图中的字母并不代表结点Key的大小。R表示替代结点,P表示替代结点的父结点,S表示替代结点的兄弟结点,SL表示兄弟结点的左子结点,SR表示兄弟结点的右子结点。灰色结点表示它可以是红色也可以是黑色。

        值得特别提醒的是,R是即将被替换到删除结点的位置的替代结点,在删除前,它还在原来所在位置参与树的子平衡,平衡后再替换到删除结点的位置,才算删除完成。

        这句话应该这么理解:R就是即将要被删除的节点了,转到指定位置就相当于删除了,然后把R的值给被替换节点即可。即,旋转结束后,R被删除。

以下全部归结的情景1,即要删除的替换节点没有孩子节点。

删除情景1:替换结点是红色结点

        直接删掉

删除情景2:替换结点是黑结点

        当替换结点是黑色时,我们就不得不进行自平衡处理了。我们必须还得考虑替换结点是其父结点的左子结点还是右子结点,来做不同的旋转操作,使树重新平衡。

        删除情景2.1:替换结点是其父结点的左子结点
                删除情景2.1.1:替换结点的兄弟结点是红结点
                若兄弟结点是红结点,那么根据性质4,兄弟结点的父结点和子结点肯定为黑色,不会有其他子情景,我们按图处理,得到删除情景2.1.2.3(后续讲解,这里先记住,此时R仍然是替代结点,它的新的兄弟结点SL和兄弟结点的子结点都是黑色)。

处理:

  • 将S设为黑色
  • 将P设为红色
  • 对P进行左旋,得到情景2.1.2.3
  • 进行情景2.1.2.3的处理

数据结构——红黑树简谈_第20张图片

                

                删除情景2.1.2:替换结点的兄弟结点是黑结点
                        当兄弟结点为黑时,其父结点和子结点的具体颜色也无法确定(如果也不考虑自底向上的情况,子结点非红即为叶子结点Nil,Nil结点为黑结点),此时又得考虑多种子情景。

                        删除情景2.1.2.1:替换结点的兄弟结点的右子结点是红结点,左子结点任意颜色
                                即将删除的左子树的一个黑色结点,显然左子树的黑色结点少1了,然而右子树又有红色结点,那么我们直接向右子树“借”个红结点来补充黑结点就好啦,此时肯定需要用旋转处理了。如图22所示。

处理:

  • 将S的颜色设为P的颜色
  • 将P设为黑色
  • 将SR设为黑色
  • 对P进行左旋

数据结构——红黑树简谈_第21张图片

        平衡后的图怎么不满足红黑树的性质?前文提醒过,R是即将替换的,它还参与树的自平衡,平衡后再替换到删除结点的位置,所以R最终可以看作是删除的。另外图2.1.2.1是考虑到第一次替换和自底向上处理的情况,如果只考虑第一次替换的情况,根据红黑树性质,SL肯定是红色或为Nil,所以最终结果树是平衡的。如果是自底向上处理的情况,同样,每棵子树都保持平衡状态,最终整棵树肯定是平衡的。后续的情景同理,不做过多说明了。

                        删除情景2.1.2.2:替换结点的兄弟结点的右子结点为黑结点,左子结点为红结点
                                兄弟结点所在的子树有红结点,我们总是可以向兄弟子树借个红结点过来,显然该情景可以转换为情景2.1.2.1。图如23所示。

处理:

  • 将S设为红色
  • 将SL设为黑色
  • 对S进行右旋,得到情景2.1.2.1
  • 进行情景2.1.2.1的处理

数据结构——红黑树简谈_第22张图片

                        删除情景2.1.2.3:替换结点的兄弟结点的子结点都为黑结点
                                好了,此次兄弟子树都没红结点“借”了,兄弟帮忙不了,找父母呗,这种情景我们把兄弟结点设为红色,再把父结点当作替代结点,自底向上处理,去找父结点的兄弟结点去“借”。但为什么需要把兄弟结点设为红色呢?显然是为了在P所在的子树中保证平衡(R即将删除,少了一个黑色结点,子树也需要少一个),后续的平衡工作交给父辈们考虑了,还是那句,当每棵子树都保持平衡时,最终整棵总是平衡的。

处理:

  • 将S设为红色
  • 把P作为新的替换结点
  • 重新进行删除结点情景处理

数据结构——红黑树简谈_第23张图片

        删除情景2.2:替换结点是其父结点的右子结点
                好啦,右边的操作也是方向相反,不做过多说明了

                删除情景2.2.1:替换结点的兄弟结点是红结点
处理:

  • 将S设为黑色
  • 将P设为红色
  • 对P进行右旋,得到情景2.2.2.3
  • 进行情景2.2.2.3的处理

数据结构——红黑树简谈_第24张图片

                删除情景2.2.2:替换结点的兄弟结点是黑结点
                        删除情景2.2.2.1:替换结点的兄弟结点的左子结点是红结点,右子结点任意颜色
处理:

  • 将S的颜色设为P的颜色
  • 将P设为黑色
  • 将SL设为黑色
  • 对P进行右旋

数据结构——红黑树简谈_第25张图片

                        删除情景2.2.2.2:替换结点的兄弟结点的左子结点为黑结点,右子结点为红结点
处理:

  • 将S设为红色
  • 将SR设为黑色
  • 对S进行左旋,得到情景2.2.2.1
  • 进行情景2.2.2.1的处理

数据结构——红黑树简谈_第26张图片

                        删除情景2.2.2.3:替换结点的兄弟结点的子结点都为黑结点
处理:

  • 将S设为红色
  • 把P作为新的替换结点
  • 重新进行删除结点情景处理

数据结构——红黑树简谈_第27张图片


 

你可能感兴趣的:(#,数据结构与算法,数据结构,算法)