TreeMap 的實現是紅黑樹算法的實現,因此要了解 TreeMap 就必須對紅黑樹有必定的瞭解。html
其實這篇博文的名字叫作:根據紅黑樹的算法來分析 TreeMap 的實現,可是爲了與 Java 提升篇系列博文保持一致仍是叫作 TreeMap 比較好。經過這篇博文你能夠得到以下知識點:java
一、紅黑樹的基本概念。算法
二、紅黑樹增長節點、刪除節點的實現過程。bash
三、紅黑樹左旋轉、右旋轉的複雜過程。數據結構
四、Java 中 TreeMap 是如何經過 put、deleteEntry 兩個來實現紅黑樹增長、刪除節點的。spa
我想經過這篇博文你對 TreeMap 必定有了更深的認識。好了,下面先簡單普及紅黑樹知識。.net
紅黑樹又稱紅-黑二叉樹,它首先是一顆二叉樹,它具體二叉樹全部的特性。同時紅黑樹更是一顆自平衡的排序二叉樹。code
咱們知道一顆基本的二叉樹他們都須要知足一個基本性質–即樹中的任何節點的值大於它的左子節點,且小於它的右子節點。cdn
按照這個基本性質使得樹的檢索效率大大提升。咱們知道在生成二叉樹的過程是很是容易失衡的,最壞的狀況就是一邊倒(只有右/左子樹),這樣勢必會致使二叉樹的檢索效率大大下降(O(n)),因此爲了維持二叉樹的平衡,大牛們提出了各類實現的算法,如:AVL,SBT,伸展樹,TREAP ,紅黑樹等等。htm
平衡二叉樹必須具有以下特性:它是一棵空樹或它的左右兩個子樹的高度差的絕對值不超過 1,而且左右兩個子樹都是一棵平衡二叉樹。也就是說該二叉樹的任何一個等等子節點,其左右子樹的高度都相近。
紅黑樹顧名思義就是節點是紅色或者黑色的平衡二叉樹,它經過顏色的約束來維持着二叉樹的平衡。對於一棵有效的紅黑樹二叉樹而言咱們必須增長以下規則:
一、每一個節點都只能是紅色或者黑色
二、根節點是黑色
三、每一個葉節點(NIL 節點,空節點)是黑色的。
四、若是一個結點是紅的,則它兩個子節點都是黑的。也就是說在一條路徑上不能出現相鄰的兩個紅色結點。
五、從任一節點到其每一個葉子的全部路徑都包含相同數目的黑色節點。
這些約束強制了紅黑樹的關鍵性質: 從根到葉子的最長的可能路徑很少於最短的可能路徑的兩倍長。結果是這棵樹大體上是平衡的。由於操做好比插入、刪除和查找某個值的最壞狀況時間都要求與樹的高度成比例,這個在高度上的理論上限容許紅黑樹在最壞狀況下都是高效的,而不一樣於普通的二叉查找樹。因此紅黑樹它是複雜而高效的,其檢索效率 O(log n)。下圖爲一顆典型的紅黑二叉樹。
對於紅黑二叉樹而言它主要包括三大基本操做:左旋、右旋、着色。
注:因爲本文主要是講解 Java 中 TreeMap,因此並無對紅黑樹進行很是深刻的瞭解和研究,若是諸位想對其進行更加深刻的研究Lz提供幾篇較好的博文:
一、紅黑樹系列集錦
三、紅黑樹
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() 方法以前,咱們先了解紅黑樹增長節點的算法。
紅黑樹在新增節點過程當中比較複雜,複雜歸複雜它一樣必需要依據上面提到的五點規範,同時因爲規則 一、二、3 基本都會知足,下面咱們主要討論規則 四、5。假設咱們這裏有一棵最簡單的樹,咱們規定新增的節點爲 N、它的父節點爲 P、P 的兄弟節點爲 U、P 的父節點爲 G。
新增N 父節點P P的兄弟U P的父節點G
public V put(K key, V value) {
//用t表示二叉樹的當前節點
Entry<K,V> t = root;
//t爲null表示一個空樹,即TreeMap中沒有任何元素,直接插入
if (t == null) {
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;
}
//cmp表示key排序的返回結果
int cmp;
//父節點
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{} 代碼塊是實現排序二叉樹的核心算法,經過該算法咱們能夠確認新增節點在該樹的正確位置。
紅黑樹是一棵平衡排序二叉樹,普通的排序二叉樹可能會出現失衡的狀況,因此下一步就是要進行調整。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) {
//將P、U設爲黑色,G設爲黑色
//將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;
}
複製代碼