图解红黑树

962 阅读20分钟

写在前面

红黑树也是一棵二叉查找树,既然有了AVL树为什么还需要红黑树呢?

之前在了平衡二叉树AVL实现中讲到了为什么使用平衡二叉树AVL(解决二叉查找树退化为类似链表的问题),最大的作用就是用于查找,其时间复杂度为O(logn),但AVL树插入或删除节点后,若使得高度之差大于1,此时,AVL树的平衡状态就被破坏,需要旋转才能达到平衡。因此如果在插入和删除频繁的场景中就需要频繁的调整使之达到平衡,性能也随之下降。

因此,红黑树的提出是为了解决平衡树在插入和删除等操作频繁的情况

在了解红黑之前先看看2-3-4树

2-3-4树

在二叉树中,每个节点有一个数据项(可以理解为节点的值,key),最多有2个子节点。如果允许每个节点可以有更多的数据项和子节点,就是多叉树,2-3-4树就属于多叉树,并且还是平衡树(B树)。

2-3-4树是一种阶(阶,可以理解为节点的分支)为4的B树,所以也可称它为4叉树,主要具备以下性质:

  1. 有1个key的节点有2个子节点,有2个key的有3个子节点,有3个key的有4个子节点,对应key的节点分别称为2节点3节点4节点,这也是为什么叫2-3-4树的原因

  2. 所有的叶子节点总在同一层

  3. 每个节点的值从左到右保持了从小到大的顺序,两个key之间的子树中所有的key一定大于它的父节点的左key,小于父节点的右key。如下

    image-20210723112628360

如下面这棵2-3-4树:

image-20210723164302764

在上图中:

有2个链接的为2节点

image-20210723110526711

有3个链接的为3节点

image-20210723164450397

有4个链接的为4节点

image-20210723110918625

插入操作

2-3-4树的插入总是在叶子节点且一般不允许插入重复的key。

若插入时的叶子节点不是满节点(即4节点),如插入数据45,如下:

image-20210723164517778

节点分裂

如果插入节点是4节点,这时候节点就需要分裂才能保证树的平衡(这里假设分裂的节点不是根节点),一个4节点可以分裂成一个根节点和两个子节点(这三个节点各含一个key)然后在子节点中插入。若分裂的节点数据分别用ABC来表示:

image-20210723164553150

若分裂节点的父节点也是满节点,则按照分裂操作继续分裂即可。

2-3-4树的删除如果感兴趣的可自己去百度,网上很多

点这

红黑树

红黑树也是一棵二叉查找树,也是一棵自平衡树,具备以下性质:

  1. 节点是红色或黑色(非黑即红)
  2. 根节点是黑色
  3. 所有的null节点称为叶子节点,且都是黑色
  4. 所有红色节点的子节点都是黑色(即没有连续的红色节点)
  5. 任意一个节点到其叶子节点的所有路劲都包含相同数目的黑色节点

如下面这个常见的红黑树:

image-20210721162917104

通过以上性质,可以推出:从根节点到叶子节点的最长路径不会超过最短路径的2倍

为什么?

根据上面的性质5我们知道下图的红黑树每条路径上都是3个黑结点。因此最短路径长度为2(没有红结点的路径)。再根据性质4(没有连续的红色节点)和性质2,3(叶子和根必须是黑结点)。那么我们可以得出:

最短路径:一条具有3个黑结点的路径上最多只能有2个红结点(红黑间隔存在),最短路径为2。 最长路径:黑深度为2(根结点也是黑色)的红黑树最长路径为4。

从这一点我们可以看出红黑树是大致平衡的。 (比平衡二叉树要差一些,AVL的平衡因子最多为1)

image-20210724022550243

注:若以下有说到性质1-5,分别表示上述性质,如性质2表示“根节点是黑色”。

2-3-4树和红黑树的等价关系

红黑树起源于2-3-4树,一颗红黑树对应一颗确定的2-3-4树,一颗2-3-4树对应多个红黑树。

上面的2-3-4树和红黑树看上去完全不同,但实际上是等价的,通过一些规则可以让2-3-4树变为红黑树,如:

image-20210724011438104

上图中3节点存在2种情况:

  • 右倾:红色在右边
  • 左倾:红色在左边

因此,这就决定了一颗红黑树对应一颗确定的2-3-4树,一颗2-3-4树对应多个红黑树。然后我们把最开始的那棵2-3-4树按照上面的规则转化为红黑树,如下:

image-20210723190151828

节点之间的转化对应如下:

image-20210723190412546

由规则生成的红黑树:

image-20210723173227852

那么到底2-3-4树和红黑树是怎么对应的?就是上面简单的替换吗?那我插入或删除的时候怎么调整,通过什么规则去调整,我们继续往下分析。

左倾红黑树

在上面等价替换中,3节点可以转化为2中不同类型的红黑树,即左倾红黑树和右倾红黑树,下面的内容主要通过左倾红黑树来讲解。

那什么是左倾红黑树呢?

一个节点要么有一个子节点,要么有两个子节点,如果有一个红色子节点,那么这个子节点只能在左边,如果有2个红色子节点,那就是两边都是红色。右倾红黑树同理:

image-20210724024352841

了解了左倾红黑树,我们再看看红黑树的调整和它以及2-3-4树有什么关系,继续往下看。

红黑树的调整

插入操作

根据上面的性质,如果我们在插入一个元素的时候违反了,此时我们需要调整才能使其遵守上述性质。而调整方式只有以下2种可能:

  • 改变节点的颜色
  • 对节点进行旋转

旋转和变色

左旋:以某个结点作为支点(旋转结点),其右子结点变为旋转结点的父结点,右子结点的左子结点变为旋转结点的右子结点,左子结点保持不变

11

右旋:以某个结点作为支点(旋转结点),其左子结点变为旋转结点的父结点,左子结点的右子结点变为旋转结点的左子结点,右子结点保持不变

这里的插入操作主要通过从插入节点的过程和左倾红黑树以及右倾红黑树的规则来分析插入过程中存在的情况。

基本定义:

  • 叔父节点:指新节点的父节点的兄弟节点(若不存在叔父节点,则用null表示)
  • 祖父节点:指新节点的父节点的父节点
  • 插入原则:插入的新节点默认为红色节点

下面的1-5序号表示从根节点(包括根节点)开始插入的第几个节点。

  1. 创建一棵红黑树(13为根节点,这里不展示null节点),根据红黑树的性质:根节点是黑色

image-20210725001223155

  1. 插入第二个节点,此时会存在两种情况

    • 若插入的节点小于13,则该节点为13的左子节点,若插入节点8,此时它就是一棵红黑树,也符合左倾红黑树的规则。

      image-20210725001243423

    • 若插入的节点大于13,则该节点为13的右子节点,若插入节点17

      image-20210725001258571

      此时,节点17为右子节点,不符合左倾红黑树的规则,那么怎么调整使其平衡?我们上图还原为一棵2-3-4树:

      image-20210724235836612

      这个2-3-4树其实就是一个3节点,按照左倾的规则(父节点黑色,子节点红色)把它变为一棵左倾红黑树:

      image-20210725000111392

      我们把对比一下2-3-4树和红黑树的插入:

      image-20210725001137167

      通过上图可以看出,对2-3-4树来说就是一个3节点,对红黑树来说就是插入一个右子节点,做一次左旋转变色。如果说不左倾的话,那就是第一种情况,既不用旋转也不用变色

    通过1,2两步操作我们可以得出第一种情形:

    情形一:插入的新节点位于树的根上或插入的新节点的父节点是黑色,无需做任何操作即可使其平衡

  2. 插入第三个节点,以节点13和17为例,此时存在3种情况

    • 若插入的节点小于13,则该节点为13的左子节点,若插入节点8

      image-20210725002255058

      根据红黑树的性质不能有连续的红色节点,所以需要对它做调整使其平衡,那么怎么调整使其平衡?我们还是还原为一棵2-3-4树:

      image-20210725002817269

      再对比一下2-3-4树和红黑树的插入:

      image-20210725003310289

      通过上图可以看出,对2-3-4树来说就是一个4节点,2-3-4树要变成一棵左倾红黑树就就是父黑子红。对红黑树来说就是插入一个左子节点,做一次右旋转然后变色。这里的变色怎么理解?就理解为2-3-4树变红黑树,父黑子红。

      由于左倾的原因,新节点为其父节点的左子节点,父节点也是其父节点的左子节点,可以得出第二种情形:

      情形二:插入的新节点的父节点是红色,叔父节点为黑色(若没有则是null节点,null节点为黑色节点) ,且新节点为其父节点的左子节点,父节点也是其父节点的左子节点。这种情形只需要一次右旋变色即可。

      这里其实可以联想一下,上面的节点13和17通过左倾使得13为17的左子节点,如果不左倾,按照插入的顺序来,应该是这样的:

      image-20210725001258571

      所以,若此时我插入节点大于17,那么刚好和情形二相反,若插入节点25:

      image-20210725231526736

      这里就可以非常简单的推出第三种情形:

      情形三:插入的新节点的父节点是红色,叔父节点为黑色(若没有则是null节点,null节点为黑色节点) ,且新节点为其父节点的右子节点,父节点也是其父节点的右子节点。这种情形需要一次左旋变色即可。

    • 若插入的节点比13大,比17小,则该节点为13的右子节点,若插入节点15

      image-20210725004305228

      肯定也是不符合红黑树的性质的,这里直接查看对比图:

      image-20210725233622498

      这种情况可以看到,插入节点还是形成了一棵2-3-4树,在按照左倾的规则,先左旋,这时就和上面的情况相同,然后再右旋,在变色,也就形成了最终的红黑树。

      情形四:插入的新节点的父节点是红色,叔父节点为黑色(若没有则是null节点,null节点为黑色节点) ,且新节点为其父节点的右子节点,父节点是其父节点的左子节点。这种情形需要先进行一次左旋,然后右旋,最后变色即可。

      同样,若不左倾,当插入节点15:

      image-20210725233217849

      情形五:插入的新节点的父节点是红色,叔父节点为黑色(若没有则是null节点,null节点为黑色节点) ,且新节点为其父节点的左子节点,父节点是其父节点的右子节点。这种情形需要先进行一次右旋,然后左旋,最后变色即可。

    • 若插入的节点比17大,则该节点为17的右子节点,若插入节点25

      image-20210725134602429

      此时,它就是一棵满足条件的红黑树,这种情形就是情形一,无需做任何操作。

      image-20210725134921926

      所以,连续插入3个节点,对于2-3-4树形成一个4节点,对于红黑树,形成都是这样:

    image-20210725011359427

  3. 插入第四个节点,以节点13,17,25为例,此时可能存在4种情况,即第四个节点可能为13的左子节点,右子节点,25的左子节点,右子节点。

    • 若为13的左子节点 ,插入节点11,即插入节点在根节点的左边

      image-20210725135207481

      上图中的变色实际就对应2-3-4树的3节点,3节点左倾变为父黑子红的左倾红黑树,2节点对应黑色节点。

    • 若为13的右子节点,插入节点15,即插入节点在根节点的左边

      image-20210725135855545

      上图中是按照左倾的方式来处理的,如果不左倾则简单很多,因为不需要左旋,直接变色即可,如下:

      image-20210726000003473

    • 若为25的左子节点 ,插入节点22,即插入节点在根节点的右边

      image-20210725140741036

    • 若为25的右子节点 ,插入节点27,即插入节点在根节点的右边

      image-20210725141647608

      如果不左倾直接变色即可:

      image-20210726000111375

      上面通过插入第四个节点的分析,可以看到新节点插入时,其父节点和叔父节点都是红色,若不考虑左倾,它们都是只做了变色操作,所以得出情形六:

      情形六:若插入节点的父节点和叔父节点都是红色,则做变色操作即可使其平衡。

至此,红黑树插入节点的情形都分析完成,如果理解了2-3-4树到红黑树的转变,就不用去死记插入节点时会碰到那些情况。

小结

通过上面插入操作的分析,插入的情形可以总结出以下几点:

  • 若插入新节点为根节点或者父节点为黑色,则无需做任何操作(黑父)
  • 若插入新节点的父节点和叔父节点都是红色,则做变色操作即可(红父红叔)
  • 若插入新节点的父节点为红色,叔父节点为黑色(红父黑叔)
    • 若新节点及左子节点,父节点为左子节点,先右旋再变色(子左父左)
    • 若新节点为右子节点,父节点为左子节点,先左旋再右旋再变色(子右父左)
    • 若新节点为左子节点,父节点为右子节点,先右旋再右旋再变色(子左父右)
    • 若新节点为右子节点,父节点为右子节点,先左旋再变色(子右父右)

删除操作

红黑树的删除如果还原为2-3-4树分析个人感觉复杂很多,所以这里还是按照二叉排序树的删除方式来分析。红黑树的删除和二叉排序树类似,也分为3种情况,即删除节点无子节点,有1个子节点和有2个子节点三中情况,也就是说红黑树的删除要先考虑是否有子节点再考虑删除节点的颜色。所以在红黑树的删除中,存在6种情形(3*2)。

无子节点

情形一:删除节点无子节点 ,且删除节点为红色

由红黑树的性质4和5可知,若删除节点为红色,则其父节点必为黑色,所以这种情况不会破坏红黑树的性质,直接删除即可。如下图删除节点27

image-20210722101144781

情形二:删除节点无子节点,且删除节点为黑色

这种情况是比较复杂的,建议先看完其他几种在回头来看。

image-20210726143613281

情形二的删除情形又分为以下几种:

① 删除节点的兄弟节点为黑色,且兄儿子与兄同边(左或右)红色

image-20210726165550088

上图中,删除节点D,则经过D路径的黑色节点减少1个,以P为支点旋转,将B节点旋转(左旋)到P的位置,

并把与B同边的BS节点变为黑色,P节点变为黑色,并将替代节点null删除,达到局部平衡。

del

同理,若节点D在右边,节点B,BS在左边,那就是右旋+变色即可达到平衡。

② 删除节点的兄弟节点为黑色,且兄儿子与兄不同边(左或右)红色

image-20210726152035819

将BS和B旋转(右旋),变色,变成①。

同理,节点D在右边,节点B,BS在左边,左旋+变色,变成①。

③ 删除节点的兄弟节点为黑色,且兄弟节点的子节点为黑色

  • 若删除节点的父节点P为红色

    image-20210726153750695

    如果删除D,经过D节点的路径上黑色就少了一个,这个时候可以把P变成黑色,这样删除D后经过D节点路径上

    的黑色节点就和原来一样了。但是这样会导致经过B节点的路径上的黑色节点数增加一个,所以这个时候可以

    再将B节点变成红色,这样路径上的黑色节点数就和原来保持一致。

  • 若删除节点的父节点P也为黑色

    image-20210726171132483

    D节点删除之后,不管怎么变色都无法使左右平衡,因为左边少了一个黑色节点,若将D的兄弟节点B变为红色,P节点的左右子树的黑色节点相同,但是经过PB路径的黑色节点会减少1,所以此时需要对P节点(把P当做要删除的节点分析,但不是真的删除)进行平衡操作。

④ 删除节点的兄弟节点为红色

若删除节点的兄弟节点为红色,则其父节点必为黑色,且B的子节点也为黑色节点

image-20210726164127530

这里我们把节点B进行左旋和变色操作:

image-20210726165421678

从上图可以看出旋转之后删除节点D的兄弟节点变为了黑色节点BSL,这就是前面分析的情形①或②。

这里就可以解释为什么红黑树的删除最多需要三次旋转

  1. 若BSL的右子节点为红色,则符合情形①,经过一次旋转变色,共2次旋转。
  2. 若BSL的左子节点为红色,则符合情形②,经过一次旋转变色符合情形①,共3次旋转。

所以整个过程只需要旋转三次即可达到平衡,这也是为什么删除要优于AVL的原因。

有一个子节点

情形三:删除节点有一个子节点,且删除节点为红色

这种情况如下:

image-20210722102431447

由红黑树的性质4和5可知,在构建一棵红黑树的时候,任意节点的路径都具有相同的黑色节点,且红色节点和红色

节点不连续,所以若删除节点为红色其子节点必为黑色,这样就破坏了性质5,所以这种情况在构建红黑树的时候

就不会存在,所以删除的情况也不会存在。

情形四:删除节点有一个子节点,且删除节点为黑色

若删除节点为黑色,则其子节点必为红色(为黑色则不满足性质5),所以删除节点后只需要用子节点替换然后变

色即可(因为黑色节点被删除,由于性质5,所以需要把子节点在变为黑色)。如删除节点8

image-20210722103928472

有两个子节点

节点的删除可以通过前驱节点(删除节点左子树中的最大值)或后继节点(删除节点右子树中的最小值)替换掉要删除的节点,若通过后继节点删除,存在2种情况:

image-20210726112115283

所以,用后继节点代替要删除节点,转化为删除后继节点,通过判断后继节点的删除来调整。

若后继节点是①的情况:

情形五:删除节点有2个子节点

  • 若后继节点successor为红色
    • 没有右子节点,对应情形一
    • 若存在右子节点,只能为黑色,不满足性质5,对应情形三
  • 若后继节点successor为黑色
    • 没有右子节点,对应情形二
    • 若存在右子节点,不能为黑色,只能为红色,对应情形四

若后继节点是②的情况:

情形六:删除节点有2个子节点

  • 若后继节点successor为红色
    • 没有子节点,对应情形一
    • 若存在子节点,只能为黑色,不满足性质5,对应情形三
  • 若后继节点successor为黑色
    • 没有子节点,对应情形二
    • 若存在子节点,只能为红色,对应情形四

有2个节点的删除通过后继节点的方式,如果是前驱节点的方式,可能又是不一样的,但分析过程却是差不多的。

小结

这里的红黑树的删除是根据删除节点是否存在子节点来分析的,但总结一下上面的六种情形,情形五和情形六同时被转化为上面的一,二,四,所以我们只需要掌握这三种即可。

判断属于那种情形的时候,通过下面的顺序:

  1. 先看删除节点的颜色
  2. 再看兄弟节点的颜色
  3. 再看兄弟子节点的颜色(兄弟子节点先看兄弟右子节点右再看兄弟左子节点
  4. 最后看父节点的颜色

总结

在学习红黑树的时候查阅了很多资料,同时也让我很头疼,比如它和AVL的增删效率到底怎么去计算,明明在新增的时候旋转的次数都是两次,有的博文却说AVL要高于2次,10篇文章就有10种说法,但是我还是坚持自己在实际操作中所记录下的。

在面试中也不会有面试官叫你手写红黑树,如果有,请把键盘给他,他行他上。而且对于我们实际开发也没有太大用处,也可能一辈子都不会去手写一个红黑树,比如你不会去自己实现HashMap,所以没有必要动不动就什么死磕红黑树的,我们只需要知道他的基本逻辑就行了,如新增的时候怎么旋转保持平衡,删除的时候怎么旋转保持平衡的逻辑就够了。而在你有一定代码能力之后,很可能你就自己能写一个简单的demo了,写代码的前提就是要清晰的知道逻辑。

这里还是把代码提供出来,需要的可自行去下载:

data-structure

之前有人问上面的动图是怎么做的,数据结构可视化:

Data Structure Visualizations

参考