根據紅黑樹的演算法來分析TreeMap的實現
TreeMap的實現是紅黑樹演算法的實現,所以要了解TreeMap就必須對紅黑樹有一定的瞭解。通過這篇博文你可以獲得如下知識點:
1、紅黑樹的基本概念。
2、紅黑樹增加節點、刪除節點的實現過程。
3、紅黑樹左旋轉、右旋轉的複雜過程。
4、Java 中TreeMap是如何通過put、deleteEntry兩個來實現紅黑樹增加、刪除節點的。
我想通過這篇博文你對TreeMap一定有了更深的認識。好了,下面先簡單普及紅黑樹知識。
一、紅黑樹簡介
紅黑樹又稱紅-黑二叉樹,它首先是一顆二叉樹,它具體二叉樹所有的特性。同時紅黑樹更是一顆自平衡的排序二叉樹。
我們知道一顆基本的二叉樹他們都需要滿足一個基本性質--即樹中的任何節點的值大於它的左子節點,且小於它的右子節點。
平衡二叉樹必須具備如下特性:它是一棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,並且左右兩個子樹都是一棵平衡二叉樹。也就是說該二叉樹的任何一個等等子節點,其左右子樹的高度都相近。
紅黑樹顧名思義就是節點是紅色或者黑色的平衡二叉樹
1、每個節點都只能是紅色或者黑色
2、根節點是黑色
3、每個葉節點(NIL節點,空節點)是黑色的。
4、如果一個結點是紅的,則它兩個子節點都是黑的。也就是說在一條路徑上不能出現相鄰的兩個紅色結點。
5、從任一節點到其每個葉子的所有路徑都包含相同數目的黑色節點。
這些約束強制了紅黑樹的關鍵性質: 從根到葉子的最長的可能路徑不多於最短的可能路徑的兩倍長。結果是這棵樹大致上是平衡的。因為操作比如插入、刪除和查詢某個值的最壞情況時間都要求與樹的高度成比例,這個在高度上的理論上限允許紅黑樹在最壞情況下都是高效的,而不同於普通的二叉查詢樹。所以紅黑樹它是複雜而高效的,其檢索效率O(log n)。下圖為一顆典型的紅黑二叉樹。
對於紅黑二叉樹而言它主要包括三大基本操作:左旋、右旋、著色。
左旋 右旋
注:由於本文主要是講解Java中TreeMap,所以並沒有對紅黑樹進行非常深入的瞭解和研究,如果諸位想對其進行更加深入的研究Lz提供幾篇較好的博文:
1、紅黑樹系列集錦
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步驟中找到的節點進行比對,如果新增節點較大,則新增為右子節點;否則新增為左子節點。
-