1. 程式人生 > >根據紅黑樹的演算法來分析TreeMap的實現

根據紅黑樹的演算法來分析TreeMap的實現

TreeMap的實現是紅黑樹演算法的實現,所以要了解TreeMap就必須對紅黑樹有一定的瞭解。通過這篇博文你可以獲得如下知識點:

       1、紅黑樹的基本概念。

       2、紅黑樹增加節點、刪除節點的實現過程。

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

       4、Java 中TreeMap是如何通過put、deleteEntry兩個來實現紅黑樹增加、刪除節點的。

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

 

       一、紅黑樹簡介

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

       我們知道一顆基本的二叉樹他們都需要滿足一個基本性質--即樹中的任何節點的值大於它的左子節點,且小於它的右子節點。

按照這個基本性質使得樹的檢索效率大大提高。我們知道在生成二叉樹的過程是非常容易失衡的,最壞的情況就是一邊倒(只有右/左子樹),這樣勢必會導致二叉樹的檢索效率大大降低(O(n)),所以為了維持二叉樹的平衡,大牛們提出了各種實現的演算法,如:AVLSBT伸展樹TREAP ,紅黑樹等等。

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

       紅黑樹顧名思義就是節點是紅色或者黑色的平衡二叉樹

,它通過顏色的約束來維持著二叉樹的平衡。對於一棵有效的紅黑樹二叉樹而言我們必須增加如下規則:

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

       2、根節點是黑色

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

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

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

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

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

左旋                                   右旋


 

       注:由於本文主要是講解Java中TreeMap,所以並沒有對紅黑樹進行非常深入的瞭解和研究,如果諸位想對其進行更加深入的研究Lz提供幾篇較好的博文:

       1、紅黑樹系列集錦

       2、紅黑樹資料結構剖析

       3、紅黑樹 

 

       二、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()方法。通過這兩個方法我們會了解紅黑樹增加、刪除節點的核心演算法。

 

       三、TreeMap put()方法

       在瞭解TreeMap的put()方法之前,我們先了解紅黑樹增加節點的演算法。

       紅黑樹增加節點

       紅黑樹在新增節點過程中比較複雜,複雜歸複雜它同樣必須要依據上面提到的五點規範,同時由於規則1、2、3基本都會滿足,下面我們主要討論規則4、5。假設我們這裡有一棵最簡單的樹,我們規定新增的節點為N、它的父節點為P、P的兄弟節點為U、P的父節點為G。

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

       1、插入新節點總是紅色節點 。

       2、如果插入節點的父節點是黑色, 能維持性質 。

       3、如果插入節點的父節點是紅色, 破壞了性質. 故插入演算法就是通過重新著色或旋轉, 來維持性質 。

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

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

2、根節點是黑色

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

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

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

 

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

           二、父節點為黑色

           這種情況新節點N同樣是直接插入,同時顏色為紅色,由於根據規則四它會存在兩個黑色的葉子節點,值為null。同時由於新增節點N為紅色,所以通過它的子節點的路徑依然會儲存著相同的黑色節點數,同樣滿足規則5。(如圖一(2))

    (圖一)

           三、若父節點P和P的兄弟節點U都為紅色

           對於這種情況若直接插入肯定會出現不平衡現象。怎麼處理?P、U節點變黑、G節點變紅。這時由於經過節點P、U的路徑都必須經過G所以在這些路徑上面的黑節點數目還是相同的。但是經過上面的處理,可能G節點的父節點也是紅色,這個時候我們需要將G節點當做新增節點遞迴處理。

           四、若父節點P為紅色,叔父節點U為黑色或者缺少,且新增節點N為P節點的右孩子

           對於這種情況我們對新增節點N、P進行一次左旋轉。這裡所產生的結果其實並沒有完成,還不是平衡的(違反了規則四),這是我們需要進行情況5的操作。

  •        五、父節點P為紅色,叔父節點U為黑色或者缺少,新增節點N為父節點P左孩子

  •  
  •        這種情況有可能是由於情況四而產生的,也有可能不是。對於這種情況先已P節點為中心進行右旋轉,在旋轉後產生的樹中,節點P是節點N、G的父節點。但是這棵樹並不規範,它違反了規則4,所以我們將P、G節點的顏色進行交換,使之其滿足規範。開始時所有的路徑都需要經過G其他們的黑色節點數一樣,但是現在所有的路徑改為經過P,且P為整棵樹的唯一黑色節點,所以調整後的樹同樣滿足規範5。

  •        上面展示了紅黑樹新增節點的五種情況,這五種情況涵蓋了所有的新增可能,不管這棵紅黑樹多麼複雜,都可以根據這五種情況來進行生成。下面就來分析Java中的TreeMap是如何來實現紅黑樹的。

  •  

           TreeMap put()方法實現分析

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

           對於排序二叉樹的建立,其新增節點的過程如下:

  •        1、以根節點為初始節點進行檢索。

  •        2、與當前節點進行比對,若新增節點值較大,則以當前節點的右子節點作為新的當前節點。否則以當前節點的左子節點作為新的當前節點。

  •        3、迴圈遞迴2步驟知道檢索出合適的葉子節點為止。

  •        4、將新增節點與3步驟中找到的節點進行比對,如果新增節點較大,則新增為右子節點;否則新增為左子節點。

  •