【算法】红黑树插入数据的情况与实现(三)

大家如果有玩魔方,我相信是可以理解我说的东西的,转魔方就是先把第一面转出来,然后把第一面作为底面,然后根据遇见的情况来转魔方(是有公式的)

该系列到现在暂只有3篇文章:

【算法】红黑树(二叉树)概念与查询(一):https://blog.csdn.net/lsr40/article/details/85230703

【算法】红黑树插入数据(变色,左旋、右旋)(二):https://blog.csdn.net/lsr40/article/details/85245027

【算法】红黑树插入数据的情况与实现(三):https://blog.csdn.net/lsr40/article/details/85266069

红黑树也是一样,我们也会遇到不同的情况,也会遇到不同情况中的不同阶段,我们对于每一种情况的处理(变色和旋转),都需要让局部满足规则5,然后再循环的向上判断刚刚的变换是否破坏了上层结构的平衡(一般就是破坏了上层的规则4)

1、会遇到的情况:

这里我总结为4种大情况在第4种情况中,有阶段1阶段2

情况1:如果是根节点,直接插入就完事了(插入还是固定为红色,然后在代码的最后把根目录设置为黑色)

情况2:插入节点的父亲,为黑色,也一样,插入就完事了,不用做任何的改动

情况3:插入节点的父亲为红色,叔叔节点(插入节点的爷爷的另一个子节点)的颜色也是红色

情况4:插入节点的父亲为红色,叔叔节点节点为黑色(这种情况最麻烦,因为需要再做一次判断)

我先来描述下:这里可能会出现4种情况(别怕,四种情况只有2种处理方式),看图:

(爷爷节点我用G表示,爸爸:F,叔叔:U,插入节点:M)

请注意!!!你发现如下的4张图U这个节点都为NIL(也就是一个不存在的空节点),不存在的空节点默认也为黑色,我强行画了出来只是为了便于理解,大家可以去:https://sandbox.runjs.cn/show/2nngvn8w 网站上自己插入试试,遇到的情况4,叔叔节点都是空的,另外我发现很多讲红黑树的文章,也把叔叔节点画成一个黑色的节点,可能会让看官产生疑惑(因为如果U是一个存在的节点,那本身就不符合规则5,然后你会发现一顿旋转操作结束,也不能符合规则5,接着就一脸懵逼),所以我这里特地标明!

图从左往右,从上到下看

图1:父节点是爷爷节点的左节点,插入节点是父节点的右节点

图2:父节点是爷爷节点的左节点,插入节点是父节点的左节点

图3:父节点是爷爷节点的右节点,插入节点是父节点的左节点

图2:父节点是爷爷节点的右节点,插入节点是父节点的右节点

是不是被绕晕了,其实就是爷爷和爸爸和插入节点要是否三点一线,如果不是三点一线,就符合情况4的第1阶段

如果是三点一线,就符合情况4的第2阶段!为什么是阶段1和阶段2呢?因为阶段1的处理方式,就是经过旋转变成阶段2,然后再做阶段2应该做的旋转!

2、对于不同情况的整理:

情况1:这棵树没有任何节点,插入的点为根节点,一开始插入红色,然后直接转为黑色

情况2:父节点是黑色,直接插入,不做任何的换色和旋转

情况3:父节点是红色,且叔叔节点也是红色,直接把叔叔和爸爸变成黑色,然后把爷爷变成和自己一样的红色,继续迭代(因为这样可能会出现爷爷和太爷爷的都是红色的情况,那么就要继续判断是哪种情况)

情况4:父节点是红色,且叔叔节点也是红色,要先判断在哪个阶段

-1.如果符合阶段1,就是图1和图3

图1就对F节点做左旋,图3就对F节点做右旋,把图形的样子旋转成阶段2

-2.如果符合阶段2,就是图2和图4

图2就对G节点做右旋,然后将G变红色,F变黑色,如下图

 

图4就对G节点做左旋,然后G变红色,F变黑色,如下图

3、代码实现

我们一起来思考下应该怎么实现?

1、外层要有循环

所以我们发现,一套变换下来,实现了局部满足红黑树的所有规则的,但是情况3将爷爷节点变成了红色,那就有可能爷爷和太爷爷变成了两个红色,相互冲突(违反规则4),所以代码应该外面有一层循环。

2、判断父节点是爷爷节点的哪边的子节点

因为情况4中,需要判断新插入的节点和父亲节点和爷爷节点是否三点一线,如果三点一线就直接进行阶段2的变换,否则要先进行阶段1,再进行阶段2。

3、其他设置

插入的节点必须为红色,代码的最后根节点必须设置为黑色,最好封装好左旋和右旋的方法,获取父节点,获取左右子节点的方法等待调用

如果上面三点大家能够想明白的话,我们再一起来看看TreeMap的put和fixAfterInsertion的源代码!

/**
  * 我相信put的代码,我就不需要过多的解释了
  * put就是判断是否有比较器,然后从根节点一层层往下遍历
  * 最后插入到最底下的节点中,关键是插入完之后
  * 调用了fixAfterInsertion方法(用来使插入节点后的树重新满足红黑树的性质)
  */
public V put(K key, V value) {
    Entry<K,V> t = root;
    if (t == null) {
        compare(key, key); // type (and possibly null) check

        root = new Entry<>(key, value, null);
        size = 1;
        modCount++;
        return null;
    }
    int cmp;
    Entry<K,V> parent;
    // split comparator and comparable paths
    Comparator<? super K> cpr = comparator;
    if (cpr != null) {
        do {
            parent = t;
            cmp = cpr.compare(key, t.key);
            if (cmp < 0)
                t = t.left;
            else if (cmp > 0)
                t = t.right;
            else
                return t.setValue(value);
        } while (t != null);
    }
    else {
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
            Comparable<? super K> k = (Comparable<? super K>) key;
        do {
            parent = t;
            cmp = k.compareTo(t.key);
            if (cmp < 0)
                t = t.left;
            else if (cmp > 0)
                t = t.right;
            else
                return t.setValue(value);
        } while (t != null);
    }
    Entry<K,V> e = new Entry<>(key, value, parent);
    if (cmp < 0)
        parent.left = e;
    else
        parent.right = e;
    fixAfterInsertion(e);
    size++;
    modCount++;
    return null;
}
  
    private void fixAfterInsertion(Entry<K,V> x) {
        //将插入的新节点,颜色设置为红色
        x.color = RED;
        /**
         * 当插入的节点x不为空,不是根节点,并且父节点是红色的
         * 因为如果父节点是黑色的,直接插入红色的新节点是不会影响红黑树的性质的,不用做任何操作
         * 所以到这里已经把情况1和情况2都过滤掉了,下面就是对情况3和情况4的处理
         * 并不是处理一次就能得到最后的结果,大家还需要注意x的重新赋值,进入下一次循环
         */
        while (x != null && x != root && x.parent.color == RED) {
            //如果x的祖父节点的左节点是x的父节点,这里的判断就是为了情况4的三点一线
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                //那设置x的祖父节点的右节点为y(就是x的叔叔节点为y)
                Entry<K,V> y = rightOf(parentOf(parentOf(x)));
                //如果叔叔y颜色是红色,那就是情况3
                if (colorOf(y) == RED) {
                    //那么就设置x的父节点为黑色
                    setColor(parentOf(x), BLACK);
                    //叔叔节点y也设置为黑色
                    setColor(y, BLACK);
                    //设置祖父节点为红色
                    setColor(parentOf(parentOf(x)), RED);
                    //把x设置为祖父节点,继续往上游循环
                    x = parentOf(parentOf(x));
                    //否则叔叔y的颜色是黑色,情况4	
                } else {
                    //如果x是父节点的右节点,三点不一线,先做情况4的阶段1
                    if (x == rightOf(parentOf(x))) {
                        //把x设置为父节点
                        x = parentOf(x);
                        //基于刚刚新设置的x节点进行左旋
                        rotateLeft(x);
                    }
                    //情况4的阶段2
                    //设置x节点为黑色
                    setColor(parentOf(x), BLACK);
                    //祖父节点为红色
                    setColor(parentOf(parentOf(x)), RED);
                    //基于祖父节点进行右旋
                    rotateRight(parentOf(parentOf(x)));
                }
                //否则就是情况4的另外两种,父节点是爷爷节点的右子节点
            } else {
                Entry<K,V> y = leftOf(parentOf(parentOf(x)));
                //如果叔叔y是红色,那就是情况3
                if (colorOf(y) == RED) {
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    //把x设置为祖父节点,继续往上游循环
                    x = parentOf(parentOf(x));
                } else {
                    //否则叔叔y是黑色,就是情况4,先判断是不是情况4的阶段1,
                    if (x == leftOf(parentOf(x))) {
                        //阶段1会把插入的节点转到上面去,所以我需要重新设置插入的节点为原来的父节点
                        x = parentOf(x);
                        rotateRight(x);
                    }
                    //执行情况4的阶段2
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateLeft(parentOf(parentOf(x)));
                }
            }
        }
        root.color = BLACK;
    }

好了,到这里我就已经把插入的情况和代码处理就讲完了!

我的思路是偏向于:https://en.wikipedia.org/wiki/Red%E2%80%93black_tree#Insertion这篇英文wiki的,我觉得它整理的很清晰,也很有助于我的学习,如果英文好的同学也可以直接去学习~

如果本文中有什么笔误,还希望大家批评指出,避免误导新人~如果有任何疑问,欢迎留言评论!

猜你喜欢

转载自blog.csdn.net/lsr40/article/details/85266069