Java集合(4)一 红黑树、TreeMap与TreeSet(下)

引言

说完了二叉树的遍历、添加和删除引伸到了红黑树的遍历、添加和删除。对二叉树结构有了必定的了解,在这篇文章中将会对红黑树性质进行详细的说明。java

红黑树

二叉树在理想状况下时间复杂度是O(logn),最坏状况下当插入的数据由小到大或者由大到小排列的时候,二叉树就变成了一个链表,而咱们知道链表检索的时间复杂度是O(n),效率很是差,因此出现了AVL树和红黑树来改变这种情况。同时因为AVL树的极端平衡特性,致使添加和删除数据后须要过多的旋转操做来保证AVL树平衡的特征,因此TreeMap中会使用红黑树来存储数据。 最好状况下的二叉树: spa

最差状况下的二叉树:

树旋转

当AVL树在添加或者删除节点时出现不平衡后经过什么操做来保证树的平衡性呢?这种操做就叫作书旋转。红黑树也是如此,不过红黑树更复杂,这点咱们后面再说,先来看看AVL树的旋转操做。 树旋转操做是因为二叉树在添加节点时为了不出现平衡失效的状况而作的一种操做,操做的基本原则是操做后不影响二叉树中序遍历的结果。 这里咱们用AVL树来讲明这个问题。AVL树是一种高度平衡的二叉树,他的任何两个节点的子树的高度最大差异为1,这样他的查找、插入和删除的时间复杂度都是O(logn),当出现不平衡状况的时候,就须要执行树旋转。3d

旋转操做

树的旋转操做分为两种,左旋转和右旋转,这两种旋转是相对的。经过右旋或者左旋操做咱们可使一棵树继续保持平衡状态,并不会改变中序遍历的结果,但同时也要付出相应的代价。 code

//右旋
private void rotateRight(Entry<K,V> p) {
    if (p != null) {
        Entry<K,V> l = p.left;
        p.left = l.right;
        if (l.right != null) l.right.parent = p;
        l.parent = p.parent;
        if (p.parent == null)
            root = l;
        else if (p.parent.right == p)
            p.parent.right = l;
        else p.parent.left = l;
        l.right = p;
        p.parent = l;
    }
}
//左旋
private void rotateLeft(Entry<K,V> p) {
    if (p != null) {
        Entry<K,V> r = p.right;
        p.right = r.left;
        if (r.left != null)
            r.left.parent = p;
        r.parent = p.parent;
        if (p.parent == null)
            root = r;
        else if (p.parent.left == p)
            p.parent.left = r;
        else
            p.parent.right = r;
        r.left = p;
        p.parent = r;
    }
}
复制代码

AVL树旋转的几种状况

当树的任何两个节点的子树的高度差大于1的时候,就须要进行旋转以保证任何两个节点的子树的高度最大差异为1。哪几种状况下须要进行树旋转操做? 1.左左状况,左节点比右节点多两个节点,而且多出的节点都在左子树; cdn

2.右右状况,右节点比左节点多两个节点,而且多出的节点都在右子树;
3.左右状况,左节点或者右节点多出两个节点,多出的第一个节点在左子树,第二个节点在右子树;
4.右左状况,左节点或者右节点多出两个节点,多出的第一个节点在右子树,第二个节点在左子树;

红黑树的特性

明白了AVL树的旋转操做,再来看红黑树就简单多了,红黑树就是一颗知足必定条件的,相对平衡的二叉树,是二叉树和AVL树的一种折中。 红黑树的添加删除操做同二叉树同样,可是当添加删除等操做后使红黑树失去了他的特性后,就须要进行旋转操做来恢复红黑树的特性。 红黑树须要知足如下几点性质: 1.每一个节点要么是红色,要么是黑色。 2.根节点永远是黑色的。 3.全部的叶节点都是空节点(即 null),而且是黑色的。 4.每一个红色节点的两个子节点都是黑色。(从每一个叶子到根的路径上不会有两个连续的红色节点) 5.从任一节点到其子树中每一个叶子节点的路径都包含相同数量的黑色节点。 性质1和性质2很好理解。性质3在Java里面指的是空节点,通常不用考虑。性质4保证了从根到叶子节点的最长路径最多只能是最短路径的两倍,根据性质5创建一颗黑色节点为3的红黑树,最短路径为黑-黑-黑,最长路径为黑-红-黑-红-黑-红(由于每一个红色节点的两个子节点都是黑色,红色则不可连续)。 下图是一颗标准的红黑树: blog

红黑树添加后修复

在上一篇文章中的添加操做后调用了fixAfterInsertion(e)方法用来修复被破坏的红黑树性质。 通常默认每一个添加的节点都为红色,由于添加的节点若是为黑色,那就必定会破坏性质5,并且很难修复。但若是添加的是红色节点,有可能就不会破坏任何性质,也可能会破坏性质4致使连续的红色节点,能够经过变色和旋转来修复。 在添加红色节点时可能会遇到如下几种状况: 1.新节点为根节点,父节点为空,破坏性质2,修复红色为黑色便可。 it

2.新节点的父节点为黑色,添加的新节点为红色,不破坏任何性质。
3.新节点的父节点为红色,同时父节点的兄弟节点也为红色(根据性质4,父节点的父节点为黑色),添加的新节点也为红色,破坏了性质4,修复父节点和父节点的兄弟节点为黑色,同时父节点的父节点为红色,保证性质5不被破坏。
4.新节点的父节点为红色,同时父节点的兄弟节点为黑色或为空(空也为黑色)。若是新节点为父节点的左节点,但新节点的父节点为祖父节点的右节点;或者新节点为父节点的右节点,但新节点的父节点为祖父节点的左节点,就须要先右旋或者左旋,而后转换成状况5,再进行一次着色和旋转。
5.新节点的父节点为红色,同时父节点的兄弟节点为黑色或为空(空也为黑色)。若是新节点为父节点的左节点,同时新节点的父节点也为祖父节点的左节点;或者新节点为父节点的右节点,同时新节点的父节点也为祖父节点的右节点。设置新节点的父节点为黑色,设置新节点的祖父节点为红色,而后左旋或者右旋便可。

private void fixAfterInsertion(Entry<K,V> x) {
    x.color = RED;
    //状况2 x.parent.color == BLACK
    while (x != null && x != root && x.parent.color == RED) {
        if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
            Entry<K,V> y = rightOf(parentOf(parentOf(x)));
            //状况3 父节点的兄弟节点也为红色 不须要旋转
            if (colorOf(y) == RED) {
                setColor(parentOf(x), BLACK);
                setColor(y, BLACK);
                setColor(parentOf(parentOf(x)), RED);
                x = parentOf(parentOf(x));
            } else {
                //状况4 父节点的兄弟节点为黑色 父节点为祖父节点的左节点 x为父节点的右节点 先左旋变为状况5
                if (x == rightOf(parentOf(x))) {
                    x = parentOf(x);
                    rotateLeft(x);
                }
                //状况5 父节点的兄弟节点为黑色 父节点为祖父节点的左节点 x也为父节点的左节点 改变父节点为黑色 祖父节点为红色 而后祖父节点右旋
                setColor(parentOf(x), BLACK);
                setColor(parentOf(parentOf(x)), RED);
                rotateRight(parentOf(parentOf(x)));
            }
        } else {
            Entry<K,V> y = leftOf(parentOf(parentOf(x)));
            //状况3
            if (colorOf(y) == RED) {
                setColor(parentOf(x), BLACK);
                setColor(y, BLACK);
                setColor(parentOf(parentOf(x)), RED);
                x = parentOf(parentOf(x));
            } else {
                //状况4 父节点的兄弟节点为黑色 父节点为祖父节点的右节点 x为父节点的左节点 先右旋变为状况5
                if (x == leftOf(parentOf(x))) {
                    x = parentOf(x);
                    rotateRight(x);
                }
                //状况5 父节点的兄弟节点为黑色 父节点为祖父节点的右节点 x也为父节点的右节点 改变父节点为黑色 祖父节点为红色 而后祖父节点左旋
                setColor(parentOf(x), BLACK);
                setColor(parentOf(parentOf(x)), RED);
                rotateLeft(parentOf(parentOf(x)));
            }
        }
    }
    //状况1
    root.color = BLACK;
}
复制代码

红黑树删除后修复

红黑树在删除后调用了fixAfterDeletion(p)用来修复被破坏的红黑树性质。 因为在删除时咱们采用后继节点替换的方法,替换以后只须要删除替换的节点便可。这样删除节点的问题就能够转换为删除至多只有1个孩子节点的问题(由于后继节点至多只有右孩子,或者没有孩子)。 删除时有如下几种状况: 1.删除的节点为红色,根据红色节点不可连续,则他的父节点和子节点都为黑色,直接用他的黑色子节点替换便可,删除了红色节点不会破坏性质5。 2.删除的节点为黑色,可是儿子为红色,直接用红色节点替换,替换后变红色节点为黑色便可。 3.删除的节点为黑色,同时儿子也为黑色,这种状况比较复杂,又能够分为几种状况: 将儿子命名为S,儿子替换后的父亲命名为F,儿子替换后的兄弟命名为B,兄弟的左节点命名为BL,兄弟的右节点命名为BR,状况3又能够分为如下几种状况: 4.F为任意色,B为红色,将F左旋转,并交换F和B的颜色,则经过各自路径的黑色节点数目不变,但S如今有了一个红色的父节点F,一个黑色的兄弟节点B,则状况能够变成五、6或者7。 io

5.F为任意色,B为黑色,BL和BR也为黑色,只须要将B的颜色设置为红色,则经过B的路径少了一个黑色节点和经过S的黑色节点相等了,但经过F的路径少了一个黑色节点,能够从新从第一种状况进行迭代。
6.F为任意色,B为黑色,BL为红色,BR为黑色,将B右旋,这样就变成了状况7。
7.F为任意色,B为黑色,BL为黑色,BR为红色,将F左旋,同时交换F和B和颜色便可。

private void fixAfterDeletion(Entry<K,V> x) {
    while (x != root && colorOf(x) == BLACK) {
        if (x == leftOf(parentOf(x))) {
            Entry<K,V> sib = rightOf(parentOf(x));
            //状况4 F为任意色,B为红色 状况能够变成五、6或者7
            if (colorOf(sib) == RED) {
                setColor(sib, BLACK);
                setColor(parentOf(x), RED);
                rotateLeft(parentOf(x));
                sib = rightOf(parentOf(x));
            }
            //状况5 F为任意色,B为黑色,BL和BR也为黑色
            if (colorOf(leftOf(sib))  == BLACK &&
                colorOf(rightOf(sib)) == BLACK) {
                setColor(sib, RED);
                x = parentOf(x);
            } else {
                //状况6 F为任意色,B为黑色,BL为红色,BR为黑色
                if (colorOf(rightOf(sib)) == BLACK) {
                    setColor(leftOf(sib), BLACK);
                    setColor(sib, RED);
                    rotateRight(sib);
                    sib = rightOf(parentOf(x));
                }
                //状况7 F为任意色,B为黑色,BL为黑色,BR为红色
                setColor(sib, colorOf(parentOf(x)));
                setColor(parentOf(x), BLACK);
                setColor(rightOf(sib), BLACK);
                rotateLeft(parentOf(x));
                x = root;
            }
        } else { // symmetric
            Entry<K,V> sib = leftOf(parentOf(x));
            //状况4 F为任意色,B为红色 状况能够变成五、6或者7
            if (colorOf(sib) == RED) {
                setColor(sib, BLACK);
                setColor(parentOf(x), RED);
                rotateRight(parentOf(x));
                sib = leftOf(parentOf(x));
            }
            //状况5 F为任意色,B为黑色,BL和BR也为黑色
            if (colorOf(rightOf(sib)) == BLACK &&
                colorOf(leftOf(sib)) == BLACK) {
                setColor(sib, RED);
                x = parentOf(x);
            } else {
                //状况6 F为任意色,B为黑色,BL为红色,BR为黑色 旋转着色后变为状况7
                if (colorOf(leftOf(sib)) == BLACK) {
                    setColor(rightOf(sib), BLACK);
                    setColor(sib, RED);
                    rotateLeft(sib);
                    sib = leftOf(parentOf(x));
                }
                //状况7 F为任意色,B为黑色,BL为黑色,BR为红色
                setColor(sib, colorOf(parentOf(x)));
                setColor(parentOf(x), BLACK);
                setColor(leftOf(sib), BLACK);
                rotateRight(parentOf(x));
                x = root;
            }
        }
    }

    setColor(x, BLACK);
}
复制代码

总结

理解了红黑树遍历,删除添加等操做的分析,再理解TreeMap<K,V>实现逻辑就会很容易。TreeMap<K,V>全部的操做都是在红黑树的基础上执行的,红黑树的每个节点对应为TreeMap<K,V>的一个Entry<K,V>。 TreeSet<E>因为在实现上彻底使用了TreeMap<K,V>的key来实现,因此TreeSet<E>的全部操做同样是创建在红黑树的基础上。function

相关文章
相关标签/搜索