Java提升篇(二七)-----TreeMap

TreeMap的實現是紅黑樹算法的實現,因此要了解TreeMap就必須對紅黑樹有必定的瞭解,其實這篇博文的名字叫作:根據紅黑樹的算法來分析TreeMap的實現,可是爲了與Java提升篇系列博文保持一致仍是叫作TreeMap比較好。經過這篇博文你能夠得到以下知識點:html

一、紅黑樹的基本概念。java

二、紅黑樹增長節點、刪除節點的實現過程。算法

三、紅黑樹左旋轉、右旋轉的複雜過程。編程

四、Java 中TreeMap是如何經過put、deleteEntry兩個來實現紅黑樹增長、刪除節點的。api

我想經過這篇博文你對TreeMap必定有了更深的認識。好了,下面先簡單普及紅黑樹知識。數據結構

1、紅黑樹簡介

紅黑樹又稱紅-黑二叉樹,它首先是一顆二叉樹,它具體二叉樹全部的特性。同時紅黑樹更是一顆自平衡的排序二叉樹。oracle

咱們知道一顆基本的二叉樹他們都須要知足一個基本性質--即樹中的任何節點的值大於它的左子節點,且小於它的右子節點。按照這個基本性質使得樹的檢索效率大大提升。咱們知道在生成二叉樹的過程是很是容易失衡的,最壞的狀況就是一邊倒(只有右/左子樹),這樣勢必會致使二叉樹的檢索效率大大下降(O(n)),因此爲了維持二叉樹的平衡,大牛們提出了各類實現的算法,如:AVLSBT伸展樹TREAP紅黑樹等等。學習

平衡二叉樹必須具有以下特性:它是一棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,而且左右兩個子樹都是一棵平衡二叉樹。也就是說該二叉樹的任何一個等等子節點,其左右子樹的高度都相近。spa

2014051700001

紅黑樹顧名思義就是節點是紅色或者黑色的平衡二叉樹,它經過顏色的約束來維持着二叉樹的平衡。對於一棵有效的紅黑樹二叉樹而言咱們必須增長以下規則:.net

一、每一個節點都只能是紅色或者黑色

二、根節點是黑色

三、每一個葉節點(NIL節點,空節點)是黑色的。

四、若是一個結點是紅的,則它兩個子節點都是黑的。也就是說在一條路徑上不能出現相鄰的兩個紅色結點。

五、從任一節點到其每一個葉子的全部路徑都包含相同數目的黑色節點。

這些約束強制了紅黑樹的關鍵性質: 從根到葉子的最長的可能路徑很少於最短的可能路徑的兩倍長。結果是這棵樹大體上是平衡的。由於操做好比插入、刪除和查找某個值的最壞狀況時間都要求與樹的高度成比例,這個在高度上的理論上限容許紅黑樹在最壞狀況下都是高效的,而不一樣於普通的二叉查找樹。因此紅黑樹它是複雜而高效的,其檢索效率O(log n)。下圖爲一顆典型的紅黑二叉樹。

2014051700002

對於紅黑二叉樹而言它主要包括三大基本操做:左旋、右旋、着色。

2014051700004                          2014051700005

左旋                                                                    右旋

(圖片來自:http://www.cnblogs.com/yangecnu/p/Introduce-Red-Black-Tree.html


本節參考文獻:http://baike.baidu.com/view/133754.htm?fr=aladdin-----百度百科

注:因爲本文主要是講解Java中TreeMap,因此並無對紅黑樹進行很是深刻的瞭解和研究,若是諸位想對其進行更加深刻的研究Lz提供幾篇較好的博文:

1紅黑樹系列集錦

二、紅黑樹數據結構剖析

三、紅黑樹

2、TreeMap數據結構

>>>>>>迴歸主角:TreeMap<<<<<<

TreeMap的定義以下:

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable

TreeMap繼承AbstractMap,實現NavigableMap、Cloneable、Serializable三個接口。其中AbstractMap代表TreeMap爲一個Map即支持key-value的集合, NavigableMap(更多)則意味着它支持一系列的導航方法,具有針對給定搜索目標返回最接近匹配項的導航方法 。

TreeMap中同時也包含了以下幾個重要的屬性:

//比較器,由於TreeMap是有序的,經過comparator接口咱們能夠對TreeMap的內部排序進行精密的控制
        private final Comparator<? super K> comparator;
        //TreeMap紅-黑節點,爲TreeMap的內部類
        private transient Entry<K,V> root = null;
        //容器大小
        private transient int size = 0;
        //TreeMap修改次數
        private transient int modCount = 0;
        //紅黑樹的節點顏色--紅色
        private static final boolean RED = false;
        //紅黑樹的節點顏色--黑色
        private static final boolean BLACK = true;

對於葉子節點Entry是TreeMap的內部類,它有幾個重要的屬性:

//
        K key;
        //
        V value;
        //左孩子
        Entry<K,V> left = null;
        //右孩子
        Entry<K,V> right = null;
        //父親
        Entry<K,V> parent;
        //顏色
        boolean color = BLACK;

注:前面只是開胃菜,下面是本篇博文的重中之重,在下面兩節我將重點講解treeMap的put()、delete()方法。經過這兩個方法咱們會了解紅黑樹增長、刪除節點的核心算法。

3、TreeMap put()方法

在瞭解TreeMap的put()方法以前,咱們先了解紅黑樹增長節點的算法。

紅黑樹增長節點

紅黑樹在新增節點過程當中比較複雜,複雜歸複雜它一樣必需要依據上面提到的五點規範,同時因爲規則一、二、3基本都會知足,下面咱們主要討論規則四、5。假設咱們這裏有一棵最簡單的樹,咱們規定新增的節點爲N、它的父節點爲P、P的兄弟節點爲U、P的父節點爲G。

2014051700007

對於新節點的插入有以下三個關鍵地方:

  • 一、插入新節點老是紅色節點 。
  • 二、若是插入節點的父節點是黑色, 能維持性質 。
  • 三、若是插入節點的父節點是紅色, 破壞了性質. 故插入算法就是經過從新着色或旋轉, 來維持性質 。

    爲了保證下面的闡述更加清晰和根據便於參考,我這裏將紅黑樹的五點規定再貼一遍:

  • 一、每一個節點都只能是紅色或者黑色

  • 二、根節點是黑色

  • 三、每一個葉節點(NIL節點,空節點)是黑色的。

  • 四、若是一個結點是紅的,則它兩個子節點都是黑的。也就是說在一條路徑上不能出現相鄰的兩個紅色結點。

  • 五、從任一節點到其每一個葉子的全部路徑都包含相同數目的黑色節點。

  • 1、爲跟節點
  • 若新插入的節點N沒有父節點,則直接當作根據節點插入便可,同時將顏色設置爲黑色。(如圖一(1))

    2、父節點爲黑色

    這種狀況新節點N一樣是直接插入,同時顏色爲紅色,因爲根據規則四它會存在兩個黑色的葉子節點,值爲null。同時因爲新增節點N爲紅色,因此經過它的子節點的路徑依然會保存着相同的黑色節點數,一樣知足規則5。(如圖一(2))

    2014051700008

    (圖一)

    3、若父節點P和P的兄弟節點U都爲紅色

    對於這種狀況若直接插入確定會出現不平衡現象。怎麼處理?P、U節點變黑、G節點變紅。這時因爲通過節點P、U的路徑都必須通過G因此在這些路徑上面的黑節點數目仍是相同的。可是通過上面的處理,可能G節點的父節點也是紅色,這個時候咱們須要將G節點當作新增節點遞歸處理。

    2014051700009

    4、若父節點P爲紅色,叔父節點U爲黑色或者缺乏,且新增節點N爲P節點的右孩子

    對於這種狀況咱們對新增節點N、P進行一次左旋轉。這裏所產生的結果其實並無完成,還不是平衡的(違反了規則四),這是咱們須要進行狀況5的操做。

    20140517000010

  • 5、父節點P爲紅色,叔父節點U爲黑色或者缺乏,新增節點N爲父節點P左孩子

    這種狀況有多是因爲狀況四而產生的,也有可能不是。對於這種狀況先已P節點爲中心進行右旋轉,在旋轉後產生的樹中,節點P是節點N、G的父節點。可是這棵樹並不規範,它違反了規則4,因此咱們將P、G節點的顏色進行交換,使之其知足規範。開始時全部的路徑都須要通過G其餘們的黑色節點數同樣,可是如今全部的路徑改成通過P,且P爲整棵樹的惟一黑色節點,因此調整後的樹一樣知足規範5。

    20140517000011

    上面展現了紅黑樹新增節點的五種狀況,這五種狀況涵蓋了全部的新增可能,無論這棵紅黑樹多麼複雜,均可以根據這五種狀況來進行生成。下面就來分析Java中的TreeMap是如何來實現紅黑樹的。

    TreeMap put()方法實現分析

    在TreeMap的put()的實現方法中主要分爲兩個步驟,第一:構建排序二叉樹,第二:平衡二叉樹。

  • 對於排序二叉樹的建立,其添加節點的過程以下:

  • 一、以根節點爲初始節點進行檢索。

  • 二、與當前節點進行比對,若新增節點值較大,則以當前節點的右子節點做爲新的當前節點。不然以當前節點的左子節點做爲新的當前節點。

  • 三、循環遞歸2步驟知道檢索出合適的葉子節點爲止。

  • 四、將新增節點與3步驟中找到的節點進行比對,若是新增節點較大,則添加爲右子節點;不然添加爲左子節點。

  • 按照這個步驟咱們就能夠將一個新增節點添加到排序二叉樹中合適的位置。以下:

  • public V put(K key, V value) {
               //用t表示二叉樹的當前節點
                Entry<K,V> t = root;
                //t爲null表示一個空樹,即TreeMap中沒有任何元素,直接插入
                if (t == null) {
                    //比較key值,我的以爲這句代碼沒有任何意義,空樹還須要比較、排序?
                    compare(key, key); // type (and possibly null) check
                    //將新的key-value鍵值對建立爲一個Entry節點,並將該節點賦予給root
                    root = new Entry<>(key, value, null);
                    //容器的size = 1,表示TreeMap集合中存在一個元素
                    size = 1;
                    //修改次數 + 1
                    modCount++;
                    return null;
                }
                int cmp;     //cmp表示key排序的返回結果
                Entry<K,V> parent;   //父節點
                // split comparator and comparable paths
                Comparator<? super K> cpr = comparator;    //指定的排序算法
                //若是cpr不爲空,則採用既定的排序算法進行建立TreeMap集合
                if (cpr != null) {
                    do {
                        parent = t;      //parent指向上次循環後的t //比較新增節點的key和當前節點key的大小
                        cmp = cpr.compare(key, t.key); //cmp返回值小於0,表示新增節點的key小於當前節點的key,則以當前節點的左子節點做爲新的當前節點
                        if (cmp < 0) t = t.left; //cmp返回值大於0,表示新增節點的key大於當前節點的key,則以當前節點的右子節點做爲新的當前節點
                        else if (cmp > 0) t = t.right; //cmp返回值等於0,表示兩個key值相等,則新值覆蓋舊值,並返回新值
                        else
                            return t.setValue(value);
                    } while (t != null);
                }
                //若是cpr爲空,則採用默認的排序算法進行建立TreeMap集合
                else {
                    if (key == null)     //key值爲空拋出異常
                        throw new NullPointerException();
                    /* 下面處理過程和上面同樣 */
                    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);
                }
                //將新增節點當作parent的子節點
                Entry<K,V> e = new Entry<>(key, value, parent);
                //若是新增節點的key小於parent的key,則當作左子節點
                if (cmp < 0)
                    parent.left = e;
              //若是新增節點的key大於parent的key,則當作右子節點
                else
                    parent.right = e;
                /*
                 *  上面已經完成了排序二叉樹的的構建,將新增節點插入該樹中的合適位置
                 *  下面fixAfterInsertion()方法就是對這棵樹進行調整、平衡,具體過程參考上面的五種狀況
                 */
                fixAfterInsertion(e);
                //TreeMap元素數量 + 1
                size++;
                //TreeMap容器修改次數 + 1
                modCount++;
                return null;
            }
    上面代碼中do{}代碼塊是實現排序二叉樹的核心算法,經過該算法咱們能夠確認新增節點在該樹的正確位置。找到正確位置後將插入便可,這樣作了其實尚未完成,由於我知道TreeMap的底層實現是紅黑樹,紅黑樹是一棵平衡排序二叉樹,普通的排序二叉樹可能會出現失衡的狀況,因此下一步就是要進行調整。fixAfterInsertion(e); 調整的過程務必會涉及到紅黑樹的左旋、右旋、着色三個基本操做。代碼以下:
  • /**
         * 新增節點後的修復操做
         * x 表示新增節點
         */
         private void fixAfterInsertion(Entry<K,V> x) {
                x.color = RED;    //新增節點的顏色爲紅色
    
                //循環 直到 x不是根節點,且x的父節點不爲紅色
                while (x != null && x != root && x.parent.color == RED) {
                    //若是X的父節點(P)是其父節點的父節點(G)的左節點
                    if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                        //獲取X的叔節點(U)
                        Entry<K,V> y = rightOf(parentOf(parentOf(x)));
                        //若是X的叔節點(U) 爲紅色(狀況三)
                        if (colorOf(y) == RED) {     
                            //將X的父節點(P)設置爲黑色
                            setColor(parentOf(x), BLACK);
                            //將X的叔節點(U)設置爲黑色
                            setColor(y, BLACK);
                            //將X的父節點的父節點(G)設置紅色
                            setColor(parentOf(parentOf(x)), RED);
                            x = parentOf(parentOf(x));
                        }
                        //若是X的叔節點(U爲黑色);這裏會存在兩種狀況(狀況4、狀況五)
                        else {   
                            //若是X節點爲其父節點(P)的右子樹,則進行左旋轉(狀況四)
                            if (x == rightOf(parentOf(x))) {
                                //將X的父節點做爲X
                                x = parentOf(x);
                                //右旋轉
                                rotateLeft(x);
                            }
                            //(狀況五)
                            //將X的父節點(P)設置爲黑色
                            setColor(parentOf(x), BLACK);
                            //將X的父節點的父節點(G)設置紅色
                            setColor(parentOf(parentOf(x)), RED);
                            //以X的父節點的父節點(G)爲中心右旋轉
                            rotateRight(parentOf(parentOf(x)));
                        }
                    }
                    //若是X的父節點(P)是其父節點的父節點(G)的右節點
                    else {
                        //獲取X的叔節點(U)
                        Entry<K,V> y = leftOf(parentOf(parentOf(x)));
                      //若是X的叔節點(U) 爲紅色(狀況三)
                        if (colorOf(y) == RED) {
                            //將X的父節點(P)設置爲黑色
                            setColor(parentOf(x), BLACK);
                            //將X的叔節點(U)設置爲黑色
                            setColor(y, BLACK);
                            //將X的父節點的父節點(G)設置紅色
                            setColor(parentOf(parentOf(x)), RED);
                            x = parentOf(parentOf(x));
                        }
                      //若是X的叔節點(U爲黑色);這裏會存在兩種狀況(狀況4、狀況五)
                        else {
                            //若是X節點爲其父節點(P)的右子樹,則進行左旋轉(狀況四)
                            if (x == leftOf(parentOf(x))) {
                                //將X的父節點做爲X
                                x = parentOf(x);
                               //右旋轉
                                rotateRight(x);
                            }
                            //(狀況五)
                            //將X的父節點(P)設置爲黑色
                            setColor(parentOf(x), BLACK);
                            //將X的父節點的父節點(G)設置紅色
                            setColor(parentOf(parentOf(x)), RED);
                            //以X的父節點的父節點(G)爲中心右旋轉
                            rotateLeft(parentOf(parentOf(x)));
                        }
                    }
                }
                //將根節點G強制設置爲黑色
                root.color = BLACK;
            }

    對這段代碼的研究咱們發現,其處理過程徹底符合紅黑樹新增節點的處理過程。因此在看這段代碼的過程必定要對紅黑樹的新增節點過程有了解。在這個代碼中還包含幾個重要的操做。左旋(rotateLeft())、右旋(rotateRight())、着色(setColor())。

    左旋:rotateLeft()

  • 所謂左旋轉,就是將新增節點(N)當作其父節點(P),將其父節點P當作新增節點(N)的左子節點。即:G.left ---> N ,N.left ---> P。

  • private void rotateLeft(Entry<K,V> p) {
            if (p != null) {
                //獲取P的右子節點,其實這裏就至關於新增節點N(狀況四而言)
                Entry<K,V> 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爲其父節點(G)的左子樹,則將R設置爲P父節點(G)左子樹
                else if (p.parent.left == p)
                    p.parent.left = r;
                //不然R設置爲P的父節點(G)的右子樹
                else
                    p.parent.right = r;
                //將P設置爲R的左子樹
                r.left = p;
                //將R設置爲P的父節點
                p.parent = r;
            }
        }
  • 右旋:rotateRight()

  • 所謂右旋轉即,P.right ---> G、G.parent ---> P。

  • private void rotateRight(Entry<K,V> p) {
            if (p != null) {
                //將L設置爲P的左子樹
                Entry<K,V> 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爲其父節點的右子樹,則將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;
            }
        }

    左旋、右旋的示意圖以下:

    2014051700004                              2014051700005

    (左旋)                                                         (右旋)

    (圖片來自:http://www.cnblogs.com/yangecnu/p/Introduce-Red-Black-Tree.html

    着色:setColor()

    着色就是改變該節點的顏色,在紅黑樹中,它是依靠節點的顏色來維持平衡的。

    private static <K,V> void setColor(Entry<K,V> p, boolean c) {
            if (p != null)
                p.color = c;
        }

    4、TreeMap delete()方法

    紅黑樹刪除節點

    針對於紅黑樹的增長節點而言,刪除顯得更加複雜,使本來就複雜的紅黑樹變得更加複雜。同時刪除節點和增長節點同樣,一樣是找到刪除的節點,刪除以後調整紅黑樹。可是這裏的刪除節點並非直接刪除,而是經過走了「彎路」經過一種捷徑來刪除的:找到被刪除的節點D的子節點C,用C來替代D,不是直接刪除D,由於D被C替代了,直接刪除C便可。因此這裏就將刪除父節點D的事情轉變爲了刪除子節點C的事情,這樣處理就將複雜的刪除事件簡單化了。子節點C的規則是:右分支最左邊,或者 左分支最右邊的。

    20140517000012

    紅-黑二叉樹刪除節點,最大的麻煩是要保持 各分支黑色節點數目相等。 由於是刪除,因此不用擔憂存在顏色衝突問題——插入纔會引發顏色衝突。

    紅黑樹刪除節點一樣會分紅幾種狀況,這裏是按照待刪除節點有幾個兒子的狀況來進行分類:

    一、沒有兒子,即爲葉結點。直接把父結點的對應兒子指針設爲NULL,刪除兒子結點就OK了。

    二、只有一個兒子。那麼把父結點的相應兒子指針指向兒子的獨生子,刪除兒子結點也OK了。

    三、有兩個兒子。這種狀況比較複雜,但仍是比較簡單。上面提到過用子節點C替代代替待刪除節點D,而後刪除子節點C便可。

    下面就論各類刪除狀況來進行圖例講解,可是在講解以前請容許我再次囉嗦一句,請時刻牢記紅黑樹的5點規定:

    一、每一個節點都只能是紅色或者黑色

    二、根節點是黑色

    三、每一個葉節點(NIL節點,空節點)是黑色的。

    四、若是一個結點是紅的,則它兩個子節點都是黑的。也就是說在一條路徑上不能出現相鄰的兩個紅色結點。

    五、從任一節點到其每一個葉子的全部路徑都包含相同數目的黑色節點。

    (注:已經講三遍了,再不記住我就懷疑你是否適合搞IT了 O(∩_∩)O~)

    誠然,既然刪除節點比較複雜,那麼在這裏咱們就約定一下規則:

    一、下面要講解的刪除節點必定是實際要刪除節點的後繼節點(N),如前面提到的C。

    二、下面提到的刪除節點的樹都是以下結構,該結構所選取的節點是待刪除節點的右樹的最左邊子節點。這裏咱們規定真實刪除節點爲N、父節點爲P、兄弟節點爲W兄弟節點的兩個子節點爲X一、X2。以下圖(2.1)。

  • 20140517000013

    如今咱們就上面提到的三種狀況進行分析、處理。

    狀況1、無子節點(紅色節點)

    這種狀況對該節點直接刪除便可,不會影響樹的結構。由於該節點爲葉子節點它不可能存在子節點-----如子節點爲黑,則違反黑節點數原則(規定5),爲紅,則違反「顏色」原則(規定4)。 如上圖(2.2)。

    狀況2、有一個子節點

    這種狀況處理也是很是簡單的,用子節點替代待刪除節點,而後刪除子節點便可。如上圖(2.3)

    狀況3、有兩個子節點

    這種狀況可能會稍微有點兒複雜。它須要找到一個替代待刪除節點(N)來替代它,而後刪除N便可。它主要分爲四種狀況。

    一、N的兄弟節點W爲紅色

    二、N的兄弟w是黑色的,且w的倆個孩子都是黑色的。

    三、N的兄弟w是黑色的,w的左孩子是紅色,w的右孩子是黑色。

    四、N的兄弟w是黑色的,且w的右孩子時紅色的。

    狀況3.一、N的兄弟節點W爲紅色

    W爲紅色,那麼其子節點X一、X2一定所有爲黑色,父節點P也爲黑色。處理策略是:改變W、P的顏色,而後進行一次左旋轉。這樣處理就可使得紅黑性質得以繼續保持。N的新兄弟new w是旋轉以前w的某個孩子,爲黑色。這樣處理後將狀況3.一、轉變爲3.二、3.三、3.4中的一種。以下:

    20140517000014

  • 狀況3.二、N的兄弟w是黑色的,且w的倆個孩子都是黑色的。

  • 這種狀況其父節點可紅可黑,因爲W爲黑色,這樣致使N子樹相對於其兄弟W子樹少一個黑色節點,這時咱們能夠將W置爲紅色。這樣,N子樹與W子樹黑色節點一致,保持了平衡。以下

  • 20140517000015

    將W由黑轉變爲紅,這樣就會致使新節點new N相對於它的兄弟節點會少一個黑色節點。可是若是new x爲紅色,咱們直接將new x轉變爲黑色,保持整棵樹的平衡。不然狀況3.2 會轉變爲狀況3.一、3.三、3.4中的一種。

  • 狀況3.三、N的兄弟w是黑色的,w的左孩子是紅色,w的右孩子是黑色。

  • 針對這種狀況是將節點W和其左子節點進行顏色交換,而後對W進行右旋轉處理。

  • 20140517000016

  • 此時N的新兄弟X1(new w)是一個有紅色右孩子的黑結點,因而將狀況3轉化爲狀況4.

  • 狀況3.四、N的兄弟w是黑色的,且w的右孩子時紅色的。

  • 交換W和父節點P的顏色,同時對P進行左旋轉操做。這樣就把左邊缺失的黑色節點給補回來了。同時將W的右子節點X2置黑。這樣左右都達到了平衡。

  • 20140517000017

  • 總結

  • 我的認爲這四種狀況比較難理解,首先他們都不是單一的某種狀況,他們之間是能夠進行互轉的。相對於其餘的幾種狀況,狀況3.2比較好理解,僅僅只是一個顏色的轉變,經過減小右子樹的一個黑色節點使之保持平衡,同時將不平衡點上移至N與W的父節點,而後進行下一輪迭代。狀況3.1,是將W旋轉將其轉成狀況二、三、4狀況進行處理。而狀況3.3經過轉變後能夠化成狀況3.4來進行處理,從這裏能夠看出狀況3.4應該最終結。狀況3.四、右子節點爲紅色節點,那麼將缺失的黑色節點交由給右子節點,經過旋轉達到平衡。

  • 經過上面的分析,咱們已經初步瞭解了紅黑樹的刪除節點狀況,相對於增長節點而言它確實是選的較爲複雜。下面我將看到在Java TreeMap中是如何實現紅黑樹刪除的。

  • TreeMap deleteEntry()方法實現分析

  • 經過上面的分析咱們確認刪除節點的步驟是:找到一個替代子節點C來替代P,而後直接刪除C,最後調整這棵紅黑樹。下面代碼是尋找替代節點、刪除替代節點。

  • private void deleteEntry(Entry<K,V> p) {
            modCount++;      //修改次數 +1
            size--;          //元素個數 -1
    
            /*
     * 被刪除節點的左子樹和右子樹都不爲空,那麼就用 p節點的中序後繼節點代替 p 節點 * successor(P)方法爲尋找P的替代節點。規則是右分支最左邊,或者 左分支最右邊的節點 * ---------------------(1) */
            if (p.left != null && p.right != null) {  
                Entry<K,V> s = successor(p);
                p.key = s.key;
                p.value = s.value;
                p = s;
            }
    
            //replacement爲替代節點,若是P的左子樹存在那麼就用左子樹替代,不然用右子樹替代
            Entry<K,V> replacement = (p.left != null ? p.left : p.right);
    
     /*
     * 刪除節點,分爲上面提到的三種狀況 * -----------------------(2) */
            //若是替代節點不爲空
            if (replacement != null) {
                replacement.parent = p.parent;
                /*
                 *replacement來替代P節點
                 */
                //若P沒有父節點,則跟節點直接變成replacement
                if (p.parent == null)
                    root = replacement;
                //若是P爲左節點,則用replacement來替代爲左節點
                else if (p == p.parent.left)
                    p.parent.left  = replacement;
              //若是P爲右節點,則用replacement來替代爲右節點
                else
                    p.parent.right = replacement;
    
                //同時將P節點從這棵樹中剔除掉
                p.left = p.right = p.parent = null;
    
                /*
                 * 若P爲紅色直接刪除,紅黑樹保持平衡
                 * 可是若P爲黑色,則須要調整紅黑樹使其保持平衡
                 */
                if (p.color == BLACK)
                    fixAfterDeletion(replacement);
            } else if (p.parent == null) {     //p沒有父節點,表示爲P根節點,直接刪除便可
                root = null;
            } else {      //P節點不存在子節點,直接刪除便可
                if (p.color == BLACK)         //若是P節點的顏色爲黑色,對紅黑樹進行調整
                    fixAfterDeletion(p);
    
                //刪除P節點
                if (p.parent != null) {
                    if (p == p.parent.left)
                        p.parent.left = null;
                    else if (p == p.parent.right)
                        p.parent.right = null;
                    p.parent = null;
                }
            }
        }

    (1)除是尋找替代節點replacement,其實現方法爲successor()。以下:

    static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
            if (t == null)
                return null;
            /*
             * 尋找右子樹的最左子樹
             */
            else if (t.right != null) {
                Entry<K,V> p = t.right;
                while (p.left != null)
                    p = p.left;
                return p;
            } 
            /*
             * 選擇左子樹的最右子樹
             */
            else {
                Entry<K,V> p = t.parent;
                Entry<K,V> ch = t;
                while (p != null && ch == p.right) {
                    ch = p;
                    p = p.parent;
                }
                return p;
            }
        }

    (2)處是刪除該節點過程。它主要分爲上面提到的三種狀況,它與上面的if…else if… else一一對應 。以下:

    一、有兩個兒子。這種狀況比較複雜,但仍是比較簡單。上面提到過用子節點C替代代替待刪除節點D,而後刪除子節點C便可。

    二、沒有兒子,即爲葉結點。直接把父結點的對應兒子指針設爲NULL,刪除兒子結點就OK了。

    三、只有一個兒子。那麼把父結點的相應兒子指針指向兒子的獨生子,刪除兒子結點也OK了。

    刪除完節點後,就要根據狀況來對紅黑樹進行復雜的調整:fixAfterDeletion()。

    private void fixAfterDeletion(Entry<K,V> x) {
            // 刪除節點須要一直迭代,知道 直到 x 不是根節點,且 x 的顏色是黑色
            while (x != root && colorOf(x) == BLACK) {
                if (x == leftOf(parentOf(x))) {      //若X節點爲左節點
                    //獲取其兄弟節點
                    Entry<K,V> sib = rightOf(parentOf(x));
    
                    /*
                     * 若是兄弟節點爲紅色----(狀況3.1)
                     * 策略:改變W、P的顏色,而後進行一次左旋轉
                     */
                    if (colorOf(sib) == RED) {     
                        setColor(sib, BLACK);     
                        setColor(parentOf(x), RED);  
                        rotateLeft(parentOf(x));
                        sib = rightOf(parentOf(x));
                    }
    
                    /*
                     * 若兄弟節點的兩個子節點都爲黑色----(狀況3.2)
                     * 策略:將兄弟節點編程紅色
                     */
                    if (colorOf(leftOf(sib))  == BLACK &&
                        colorOf(rightOf(sib)) == BLACK) {
                        setColor(sib, RED);
                        x = parentOf(x);
                    } 
                    else {
                        /*
                         * 若是兄弟節點只有右子樹爲黑色----(狀況3.3)
                         * 策略:將兄弟節點與其左子樹進行顏色互換而後進行右轉
                         * 這時狀況會轉變爲3.4
                         */
                        if (colorOf(rightOf(sib)) == BLACK) {
                            setColor(leftOf(sib), BLACK);
                            setColor(sib, RED);
                            rotateRight(sib);
                            sib = rightOf(parentOf(x));
                        }
                        /*
                         *----狀況3.4
                         *策略:交換兄弟節點和父節點的顏色,
                         *同時將兄弟節點右子樹設置爲黑色,最後左旋轉
                         */
                        setColor(sib, colorOf(parentOf(x)));
                        setColor(parentOf(x), BLACK);
                        setColor(rightOf(sib), BLACK);
                        rotateLeft(parentOf(x));
                        x = root;
                    }
                } 
                
                /**
                 * X節點爲右節點與其爲作節點處理過程差很少,這裏就不在累述了
                 */
                else {
                    Entry<K,V> 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);
        }

    這是紅黑樹在刪除節點後,對樹的平衡性進行調整的過程,其實現過程與上面四種複雜的狀況一一對應,因此在這個源碼的時候必定要對着上面提到的四種狀況看。

  • 5、寫在最後

  • 這篇博文確實是有點兒長,在這裏很是感謝各位看客可以靜下心來讀完,我想你經過讀完這篇博文必定收穫不小。同時這篇博文很大篇幅都在闡述紅黑樹的實現過程,對Java 的TreeMap聊的比較少,可是我認爲若是理解了紅黑樹的實現過程,對TreeMap那是手到擒來,小菜一碟。

    同時這篇博文我寫了四天,看了、參考了大量的博文。同時難免會有些地方存在借鑑之處,在這裏對其表示感謝。LZ大二開始學習數據結構,自認爲學的不錯,如今發現數據結構我還有太多的地方須要學習了,同時也再一次體味了算法的魅力!!!!

     


    參考資料:

    一、紅黑樹數據結構剖析:http://www.cnblogs.com/fanzhidongyzby/p/3187912.html

    二、紅黑二叉樹詳解及理論分析 :http://blog.csdn.net/kartorz/article/details/8865997

    三、教你透徹瞭解紅黑樹 blog.csdn.net/v_july_v/article/details/6105630

    四、經典算法研究系列:5、紅黑樹算法的實現與剖析 :http://blog.csdn.net/v_JULY_v/article/details/6109153

    五、示例,紅黑樹插入和刪除過程:http://saturnman.blog.163.com/blog/static/557611201097221570/

    六、紅黑二叉樹詳解及理論分析 :http://blog.csdn.net/kartorz/article/details/8865997

    相關文章
    相關標籤/搜索