以一种简单的方式来理解RBT的add和remove过程

509 阅读7分钟

本文已参与新人创作礼活动,一起开启掘金创作之路。

RBT定义

  上一篇文章讲了 AVL树的原理及Java实现 ,AVL树在 add 和 remove 之后是通过节点的左右子节点高度差来判断调节节点的平衡;红黑树RBT,也是自平衡的二叉搜索树;RBT是通过下列条件来约束节点实现的平衡:

  • 根节点是黑色;
  • 叶子节点是黑色;
  • RBT树的节点颜色只有红,黑色;
  • 一条路径上不能出现连续红色;
  • 任意节点到叶子节点的所有路径上黑色节点数相同;

对RBT中定义的解释:

  1. RBT的叶子节点

  1. 连续红色节点

  • 任意节点到叶子节点的所有路径上黑色节点数相同


  这是一颗红黑树,可以观察到任意一个节点到叶子节点的路径上黑色的节点数都是相等的;以500,200节点为例:
  • 根节点500到叶子节点的路径一共10条;这10条路径上每一条路径的黑色节点数都是都是3个;

  • 200节点到叶子节点的路径有4条,这4条路径从叶子到200节点的黑色节点都是 2个;RBT的所有节点都遵守这个规则;

  通过上面对定义的解释相信你已经掌握了红黑树的定义了。下面会分别分析在add,remove破坏红黑树定义时该如何处理。以及为什么要这么处理?弄懂了这两个问题才算是知其然,知其所以然。

RBT在add时遇到颜色冲突及解决办法


  这是target节点与其他节点关系的代称,下面描述会用到;
  • parent 父节点
  • brother 兄弟节点
  • grandpa 祖父节点
  • uncle 叔父节点或者 parent的兄弟节点

新增节点颜色

  当有新节点加入RBT时,新节点的颜色应该是黑色还是红色呢?

  以上图为例,当新节点为黑色那么必然导致其父节点的左右分支黑色节点数不等;当新节点为红色,不会影响其父节点左右分支的黑色数;如果p是黑色,则不用调整红黑树;但是如果p是红色,则造成连续红色冲突。这个时候需要调整红黑树;

  也就是说,当新节点为红色时可能导致红黑树调整;但如果新节点是黑色,则必须对红黑树调整;因此,我们在新插入节点时,将新节点的颜色染成红色这样可以减少红黑树调整的机率;

  既然新加入RBT的节点颜色是红色,这样可能与其parent节点造成连续红色的颜色冲突。那该如何调整节点颜色呢?可以直接修改节点颜色吗?
在这里插入图片描述


  上图可以看到,这样直接修改节点颜色虽然是可以解决颜色冲突的问题。但是这样改,又引入了新的问题;直接修改225的颜色,会导致250的左右分支黑色节点数不等;直接修改250的颜色会导致200的左右分支黑色节点数不等;

  直接修改是不行的,那如何修改呢?既然不能直接修改颜色,那如果可以将红色节点转移到另一个分支,那岂不完美解决?那如何转移呢?---> 通过旋转再染色可以做到。

1.png

  通过上面的2个步骤完美的将一个红色节点转移了,看起来是完美的解决了红色冲突问题。but,聪明的你或许已经注意到了图中的uncle节点,uncle节点颜色有2种可能:红色,黑色;当uncle节点是黑色时,上面的做法没问题;但是当uncle为红色时,上图做法会导致grandpa节点与uncle节点颜色冲突;

未命名绘图-第 2 页 .png

  可以看出当uncle为红色时就不能将红色节点转移到uncle所在分支了。那该如何做呢?当uncle为红色时处理会更简单: 直接将parent,uncle染成黑色,grandpa染成红色 ;由于grandpa变红,这个时候应该继续向上判断grandpa节点时候与其父节点是否有颜色冲突;如果没有冲突,就结束调整;如果有冲突,那就按照上面解决颜色冲突的方法解决冲突;

6 .png

总结一下,在解决add过程中造成的颜色冲突时,要结合uncle节点的颜色来判断使用哪种解决办法。在上诉例子中,由grandpa -> parent ->target 构成的 LL型颜色冲突; 实际可能会有四种类型的冲突,下面图解四种冲突以及解决办法:

  • uncle节点是黑色的处理;

在这里插入图片描述

  • uncle是红色的处理

在这里插入图片描述


RBT在delete时的颜色调整

  在delete时,删除的是红色节点就不会破坏红黑树的定义;如果删除黑色节点,就会破坏最后一条规则了;那如何解决黑色节点被删除的问题呢?经过被删除黑色节点的分支,比不经过该节点的分支少了一个黑色节点。这种情况有2种解决办法:

  • 1.通过旋转向兄弟节点或兄弟的子节点借一个红色节点到被删除节点所在分支将其染黑;这样可以使兄弟分支的黑色节点数不变又让被删除分支补充了一个黑色节点,保持黑色节点的平衡;

  • 2.如果兄弟节点或者兄弟节点的子节点没有红色节点,就让兄弟节点也减少一个黑色节点(把兄弟节点染红);这样可以保持被删除节点的parent节点左右分支的黑色节点数相等;

    • 2.1如果parent是红色,直接将parent染黑,增加一个黑色节点即可达到平衡;
    • 2.2如果parent是黑色,grandpa节点就左右失衡了,因此还要继续向上向uncle分支借红色节点;在这个过程中借不到节点就把兄弟变红,直到借到红色节点或者到root节点为止;

图解上面2大类情况,第一种情况分为:兄弟红色向兄弟节点借,兄弟节点的子节点有红色向子节点借;

  • 1.1 brother是红色向兄弟节点借一个节点

在这里插入图片描述

  • 1.2 brother是黑色,并且有红色的子节点;向兄弟的子节点借一个节点;(PS:这里要注意红色子节点的位置,也是分为 RR,LL,RL ,LR四种类型,不同类型旋转次数不同,下面这个例子是RR 型;后面还会再讲RL)

在这里插入图片描述

  • 2.1 兄弟及其子节点都是黑色且parent是红色,直接染黑 parent

在这里插入图片描述

  • 2.2 兄弟及其子节点都是黑色,parent是黑色;继续向上借直到root或者借到节点为止;其实这种情况又回到了前面的几种类型了;

在这里插入图片描述

以上就是红黑树对于delete时,删除了黑色节点时的处理方法;处理方式总共就2种不同类型:

  • 在删除节点所在分支增加一个黑色节点,保持黑色节点数不变;
  • 不能增加该分支的黑色节点就让另一个分支也减少一个黑色节点,以此来达到平衡;

  上面在1.2节点讲到,在向兄弟子节点借红色节点时要注意红色节点的位置;这个其实跟add操作一样,分四种类型:LL ,LR ,RR ,RL ;其中LL ,RR只需一次旋转;RL ,LR 需要2次旋转;下面以RL为例:

在这里插入图片描述

以上是对红黑树add和remove的分析,代码:Gitee-RBT

测试:

    
    @Test
    public void test(){
        for (int i = 0; i < 10; i++) {
            addVal(i);
        }
        System.out.println("++++++++++++++++add test+++++++++++++++");
       
        treePrint();

        System.out.println("\n++++++++++++++++delete test+++++++++++++++");
        deleteVal(8);
        treePrint();
    }

测试结果可以在网站:test网站对比查看结果;