Java集合——TreeMap(二)

简述

在上篇中围绕源码讲了TreeMap的一些方法,本文主要阐述其如何维持特性:
①.节点是红色或黑色
②.根节点是黑色
③.每一个叶节点(NIL节点,空节点)是黑色的
④.每一个红色节点的两个子节点都是黑色。(从每一个叶子到根的全部路径上不能有两个连续的红色节点)
⑤.从任一节点到其每一个叶子的全部路径都包含相同数目的黑色节点
注意:
性质1和性质3老是保持着
性质4只在增长红色节点、重绘黑色节点为红色,或作旋转时受到威胁
性质5只在增长黑色节点、重绘红色节点为黑色,或作旋转时受到威胁
post

新增平衡

private void fixAfterInsertion(Entry x) {
        // 新插入的节点默认红色
        x.color = RED;
        
        while (x != null && x != root && x.parent.color == RED) {
            // 若x的父节点是其父节点的父节点的左子树
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                //获取x父节点的父节点的右子树y
                Entry y = rightOf(parentOf(parentOf(x)));
                //若y颜色为红色
                if (colorOf(y) == RED) {
                    //x父节点的颜色设为黑色
                    setColor(parentOf(x), BLACK);
                    //y的颜色设为黑色
                    setColor(y, BLACK);
                    //x父节点的父节点颜色设为红色
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    //若x为其父节点的右子树
                    if (x == rightOf(parentOf(x))) {
                        x = parentOf(x);
                        //左旋转
                        rotateLeft(x);
                    }
                    //x父节点的颜色设为黑色
                    setColor(parentOf(x), BLACK);
                    //x父节点的父节点颜色设为红色
                    setColor(parentOf(parentOf(x)), RED);
                    rotateRight(parentOf(parentOf(x)));
                }
            } else {
                //获取x父节点的父节点的左子树y
                Entry y = leftOf(parentOf(parentOf(x)));
                //若y颜色为红色
                if (colorOf(y) == RED) {
                    //x父节点设为黑色
                    setColor(parentOf(x), BLACK);
                    //y颜色设为黑色
                    setColor(y, BLACK);
                    //x父节点的父节点颜色设为红色
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                //若y颜色为黑色    
                } else {
                    //若x为父节点的左子树
                    if (x == leftOf(parentOf(x))) {
                        x = parentOf(x);
                        //右旋转
                        rotateRight(x);
                    }
                    //x父节点颜色设为黑色
                    setColor(parentOf(x), BLACK);
                    //x父节点的父节点颜色设为红色
                    setColor(parentOf(parentOf(x)), RED);
                    rotateLeft(parentOf(parentOf(x)));
                }
            }
        }
        //根节点必须为黑色
        root.color = BLACK;
    } 
复制代码

新增有三点:
①.新增结点颜色为红色(方法中第一行)
②.若新增结点的父节点颜色是黑色,能够维持性质
③.如有任何红黑性质被破坏,则至多只有一条被破坏,或是性质2,或是性质4。若性质2被破坏,其缘由为新增结点是根节点且颜色为红,若性质4被破坏,其缘由为新增结点与其父节点颜色都为红色spa

场景一:新增节点为根节点

此情形不会调用到fixAfterInsertion方法,在put方法中直接经过Entry类构造方法建立根节点,建立出的结点默认黑色,符合红黑树性质无需平衡。
put方法部分代码:
3d

public V put(K key, V value) {
    Entry t = root;
        if (t == null) {
            compare(key, key); // type (and possibly null) check
            //键,值,父节点
            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        } 
        .
        .
        .
}
复制代码

场景二:新增节点的父节点颜色为黑色

此状况也无需平衡不会走进fixAfterInsertion方法循环体内,由于新增节点老是红色,又由于性质4其会有两个黑色NIL节点,因此经过它的每一个子节点的路径依然包含相同数目的黑色节点知足性质5。
实例如图添加节点12:
code

插入后:
若节点12为黑色如图,咱们能够发现结点11至其叶子节点数目明显不一样:

场景三:新增节点的父节点颜色与其叔父节点(一个节点的父节点的兄弟节点)都是红色

如图添加节点19,咱们能够发现其违反了性质4(每一个红色节点的两个子节点都是黑色)。 cdn

结合代码

if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                //获取x的叔父节点
                Entry y = rightOf(parentOf(parentOf(x)));
                //若y颜色为红色
                if (colorOf(y) == RED) {
                    //x父节点的颜色设为黑色
                    setColor(parentOf(x), BLACK);
                    //y的颜色设为黑色
                    setColor(y, BLACK);
                    //x父节点的父节点颜色设为红色
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                }
    }            
复制代码

先将父节点与叔父节点颜色改成黑色,祖父结点颜色改成红色,结果如图:
blog

咱们发现祖父结点25与其父节点17又违反了性质4再次相同操做,将父节点与叔父节点颜色改成黑色,祖父结点颜色改成红色,最后执行最后一行代码,根节点改黑色,结果如图:

场景四:新增节点父节点是红色而叔父节点是黑色或缺乏,而且新节点是其父节点的右子树而父节点又是其父节点的左子树

实例如图添加节点15:
get

结合代码先进行一次左旋转,p为图中节点14:

if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                Entry y = rightOf(parentOf(parentOf(x)));
                if (colorOf(y) == RED) {
                    .
                    .
                    .
                } else {
                    if (x == rightOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateLeft(x);
                    }
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateRight(parentOf(parentOf(x)));
                }
            }
    }        
    
    /**
     * 左旋转
     */
    private void rotateLeft(Entry p) {
        if (p != null) {
            //获取p的右子树r
            Entry r = p.right;
            //将r的左子树设为p的右子树
            p.right = r.left;
            //若r的左子树不为空,则将p设为r左子树的父节点
            if (r.left != null)
                r.left.parent = p;
            //将p的父节点设为r的父节点    
            r.parent = p.parent;
            //若p的父节点为空,则r设为根节点
            if (p.parent == null)
                root = r;
            //若p为其父节点左子树,则将r设为p父节点左子树    
            else if (p.parent.left == p)
                p.parent.left = r;
            //不然r设为p的父节点的右子树
            else
                p.parent.right = r;
            将p设为r的左子树    
            r.left = p;
            将r设为p的父节点
            p.parent = r;
        }
    }  
复制代码

左旋转动态图以下: 源码

通过左旋转后:
随后将x(节点14)父节点颜色改成黑色,祖父节点颜色改成红色,以祖父节点进行右旋转

/**
     * 右旋转
     */
    private void rotateRight(Entry p) {
        if (p != null) {
            //获取p的左子树l
            Entry l = p.left;
            将l的右子树设为p的左子树
            p.left = l.right;
            //若l的右子树不为空,则将p设为l的右子树的父节点
            if (l.right != null)
                l.right.parent = p;
            //将p的父节点设为l的父节点
            l.parent = p.parent;
            //若p的父节点为空,则将l设为根节点
            if (p.parent == null)
                root = l;
            //若p为p父节点的右子树,则将l设置为p的父节点的右子树    
            else if (p.parent.right == p)
                p.parent.right = l;
            //不然将l设为p的父节点的左子树    
            else p.parent.left = l;
            //将p设为l的右子树
            l.right = p;
            //将l设为p的父节点
            p.parent = l;
        }
    } 
复制代码

右旋转动态图以下: it

通过这些处理后,符合红黑树性质:

场景五:新增节点父节点是红色,而叔父节点是黑色或缺乏,新节点是其父节点的左子树,而父节点又是其父节点的左子树
实例如图添加节点14:
io

结合代码由于是父节点左子树,全部不须要左旋转,直接将其父节点改黑,祖父节点改红,以祖父节点右旋转:

if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                Entry y = rightOf(parentOf(parentOf(x)));
                if (colorOf(y) == RED) {
                    .
                    .
                    .
                } else {
                    if (x == rightOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateLeft(x);
                    }
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateRight(parentOf(parentOf(x)));
                }
    }  
复制代码

操做后如图,符合红黑树性质:

删除平衡

删除平衡源码:

private void fixAfterDeletion(Entry x) {
        while (x != root && colorOf(x) == BLACK) {
            //若x为其父节点的左子树
            if (x == leftOf(parentOf(x))) {
                //获取x兄弟节点 
                Entry sib = rightOf(parentOf(x));
                //若兄弟节点颜色为红色
                if (colorOf(sib) == RED) {
                    //将兄弟节点改成黑色
                    setColor(sib, BLACK);
                    //将父节点改成红色
                    setColor(parentOf(x), RED);
                    //以父节点左旋转
                    rotateLeft(parentOf(x));
                    sib = rightOf(parentOf(x));
                }
                //若兄弟节点的左子树与右子树都为黑色
                if (colorOf(leftOf(sib))  == BLACK &&
                    colorOf(rightOf(sib)) == BLACK) {
                    setColor(sib, RED);
                    x = parentOf(x);
                } else {
                    //若只有兄弟节点的右子树颜色为黑色
                    if (colorOf(rightOf(sib)) == BLACK) {
                        setColor(leftOf(sib), BLACK);
                        setColor(sib, RED);
                        rotateRight(sib);
                        sib = rightOf(parentOf(x));
                    }
                    setColor(sib, colorOf(parentOf(x)));
                    setColor(parentOf(x), BLACK);
                    setColor(rightOf(sib), BLACK);
                    rotateLeft(parentOf(x));
                    x = root;
                }
            //若x为其父节点的右子树    
            } else { // symmetric
                Entry sib = leftOf(parentOf(x));
                if (colorOf(sib) == RED) {
                    setColor(sib, BLACK);
                    setColor(parentOf(x), RED);
                    rotateRight(parentOf(x));
                    sib = leftOf(parentOf(x));
                }
                if (colorOf(rightOf(sib)) == BLACK &&
                    colorOf(leftOf(sib)) == BLACK) {
                    setColor(sib, RED);
                    x = parentOf(x);
                } else {
                    if (colorOf(leftOf(sib)) == BLACK) {
                        setColor(rightOf(sib), BLACK);
                        setColor(sib, RED);
                        rotateLeft(sib);
                        sib = leftOf(parentOf(x));
                    }
                    setColor(sib, colorOf(parentOf(x)));
                    setColor(parentOf(x), BLACK);
                    setColor(leftOf(sib), BLACK);
                    rotateRight(parentOf(x));
                    x = root;
                }
            }
        }
        setColor(x, BLACK);
    } 
复制代码

在上篇文章所提到删除节点的状况:
①.叶子结点:直接将其父节点对应孩子置空,若删除左叶子结点则将其父结点左子树置空,若删除右叶子结点则将其父结点右子树置空
②.一个孩子:用子节点替代需删除节点
③.两个孩子:用后继节点替换待删除节点,而后删除后继节点

场景一:删除叶子节点

1.若叶子节点为红色,如图删除节点12

删除后:

此状况无需平衡,结合deleteEntry方法其不会调用到fixAfterDeletion方法

2.1.若叶子节点为黑色左子树且其兄弟节点(黑色)的两个子节点都为黑色或无孩子

如上图删除节点1,结合代码,将节点1兄弟节点11改红色,再最后将其父节点8改红色:

//sib兄弟节点(这里只节点11)
    if (colorOf(leftOf(sib))  == BLACK &&
                    colorOf(rightOf(sib)) == BLACK) {
                    //将兄弟节点改成红色 
                    setColor(sib, RED);
                    //删除的节点(即节点1)指向父节点(节点8)
                    x = parentOf(x);
                }
    }      
    .
    .
    .
    setColor(x, BLACK);
复制代码

2.2.若叶子节点为黑色左子树且其兄弟节点(黑色)左子树为红色,右子树为黑色

如图删除节点1:

Entry sib = rightOf(parentOf(x));
    .
    .
    .
     if (colorOf(rightOf(sib)) == BLACK) {
        setColor(leftOf(sib), BLACK);
        setColor(sib, RED);
        rotateRight(sib);
        sib = rightOf(parentOf(x));
    }
    setColor(sib, colorOf(parentOf(x)));
    setColor(parentOf(x), BLACK);
    setColor(rightOf(sib), BLACK);
    rotateLeft(parentOf(x));
    x = root;
    .
    .
    .
    setColor(x, BLACK);
复制代码

此场景走此源码分支代码,兄弟节点左子树改(节点9)为黑色,兄弟节点(节点11)改成红色,以兄弟节点右旋转,再将其指向旋转后的兄弟节点,if分支代码走完,操做后如图:

再将旋转后的兄弟节点(节点9)颜色改成父节点(节点8)颜色(红色),将父节点改成黑色,将兄弟节点右子树(节点11)改成黑色,以父节点左转,最后x指向root退出循环且运行最后一行确保根节点黑色

2.3.若叶子节点为黑色左子树且其兄弟节点(黑色)右子树为红色,左子树为黑色:

此状况就是不走上述if分支其他同样再也不过多写了

2.4.若叶子节点为黑色左子树且其兄弟节点(红色):

如图删除节点11:

走以下代码:

if (x == leftOf(parentOf(x))) {
                Entry sib = rightOf(parentOf(x));
                if (colorOf(sib) == RED) {
                    setColor(sib, BLACK);
                    setColor(parentOf(x), RED);
                    rotateLeft(parentOf(x));
                    sib = rightOf(parentOf(x));
                }
                if (colorOf(leftOf(sib))  == BLACK &&
                    colorOf(rightOf(sib)) == BLACK) {
                    setColor(sib, RED);
                    x = parentOf(x);
                } else {
                    if (colorOf(rightOf(sib)) == BLACK) {
                        setColor(leftOf(sib), BLACK);
                        setColor(sib, RED);
                        rotateRight(sib);
                        sib = rightOf(parentOf(x));
                    }
                    setColor(sib, colorOf(parentOf(x)));
                    setColor(parentOf(x), BLACK);
                    setColor(rightOf(sib), BLACK);
                    rotateLeft(parentOf(x));
                    x = root;
                }
    }
    setColor(x, BLACK);
复制代码

将兄弟节点(节点15)改成黑色,父节点(节点13)改成红色,以父节点左旋转,旋转后:

此时兄弟节点变为节点14两孩子都为黑色,进入此分支代码将兄弟节点改成红色,x(节点11)指向父节点,将其颜色改成黑色

注意这里只列了兄弟节点红色且其两个孩子都为黑色或无孩子,其余状况能够转换成2.2,2.3

场景二:删除一个孩子节点

此场景用子节点替代需删除节点,平衡较为简单。

如上图删除节点17(红色),只需将根节点右子树设为节点15,红黑树性质依旧保持无需平衡
删除节点8(黑色),将节点11左子树设为节点1,由于节点1,11都为红色违反性质4(每一个红色节点的两个子节点都是黑色),进行调色平衡节点1改成黑色便可,删除后

场景三:删除两个孩子节点

1.删除节点的后继为红色

此状况能够转换成场景一中1处理

2.1.删除节点后继节点为黑色,后继节点的兄弟节点为黑色且其两个子节点都为黑色或无孩子

能够转换成场景一中2.1处理

2.2.删除节点后继节点为黑色,后继节点的兄弟节点为黑色且其左子树为红色,右子树为黑色

能够转换成场景一中2.2处理

2.3.删除节点后继节点为黑色,后继节点的兄弟节点为黑色且其左子树为黑色,右子树为红色

能够转换成场景一中2.3处理

2.4 删除节点后继节点为黑色,后继节点的兄弟节点为红色

能够转换成场景一中2.4处理

总结

本文列举TreeMap插入删除平衡操做结合源码阐述其原理,如有错误望指正。。。

参考

维基百科——红黑树

相关文章
相关标签/搜索