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

相關文章
相關標籤/搜索