1、TreeMap類的說明
TreeMap是一種底層採用紅黑樹結構的哈希存儲,數據內部保持有序,這個類繼承了abstractMap和實現了NavigableMap。而NavigableMap又是拓展了SortedMap,具有了針對給定搜索目標返回最接近匹配項的導航方法,方法 lowerEntry、floorEntry、ceilingEntry 和 higherEntry 分別返回與小於、小於等於、大於等於、大於給定鍵的鍵關聯的 Map.Entry 對象,如果不存在這樣的鍵,則返回 null。類似地,方法 lowerKey、floorKey、ceilingKey 和 higherKey 只返回關聯的鍵。所有這些方法是爲查找條目而不是遍歷條目而設計的。
1.1 解釋說明
/**
* 紅黑樹是基於NavigableMap實現的,這個map是通過它的鍵進行比較做自然排序的。
* 在創建映射時提供的Comparator是取決於構造函數的。
*
* 對於containsKey,get,put,remove等操作提供一個固定的log(n)的時間複雜度(因爲底層是紅黑樹)
* 這個算法是改編自Cormen, Leiserson, and Rivest這三位大佬的算法導論。
*
* 注意如果要正確實現map接口,樹形map維護的順序(與任何排序map一樣),以及是否提
* 供顯著的比較器,都必須與equals()保持一致。參閱comparable和comparator,以獲得
* 於equals()一致的精確定義,這是因爲Map接口在equals上操作定義的,但是排序map執行
* 的所有key比較是使用它的compareTo()或者compare()方法的,所以從排序Map角度兩個key
* 被視爲相等是通過compareTo()或者compare()來比較。排序map的這種行爲是良好的即使
* 它的排序順序於equals()不一致,它只是沒有遵守Map接口的通用規則.
*
*
* 注意這個方法不是一個同步的方法(這個跟前面的那些HashMap是一樣的。),如果有多個線程同時
* 結構性修改Map,應該自己加同步方法,這個synchronized通常在對象頭中,如果不存在則應該用
* 同步方法包裝這個map
* SortedMap m = Collections.synchronizedSortedMap(new TreeMap(...));
*
* 還要fail-fast的快速失敗方法用來當有多個線程同時對map進行修改時候拋出異常,但是不能用這
* 個機制來保證線程安全,這個只是用來檢測bug用的。(這些都跟前面的hashMap差不多的機制,可以
* 參考前面的hashMap的文章)
*
* 所有Map.Entry這個類方法中返回的條目以及這個的視圖的快照是不支持Entry.setValue,但是可以通過更改
* 映射的put()方法來執行。
*/
1.2 數據結構
TreeMap底層就是一顆紅黑樹結構。符合紅黑樹的特性。
2、方法字段
2.1 一般方法
public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{
/**
* 比較器用於維護TreeMap的鍵值映射,如果採用的是自然排序則這個
* 比較器爲空
*
* @serial
*/
private final Comparator<? super K> comparator;
//根節點
private transient Entry<K,V> root;
/**
* 樹中的節點個數
*/
private transient int size = 0;
/**
* 對這個樹做了多少次結構性修改
*/
private transient int modCount = 0;
TreeMap的字段沒有多少,comparator
字段是用來使用自定義的比較排序規則的,我們可以自己定義一個紅黑樹的節點比較規則。
3、主要方法
構造函數
/**
* 構造一個新的,空的treeMap,默認使用自然排序,所有key的插入必實現Comparable
* 接口。
*/
public TreeMap() {
comparator = null;
}
/**
*
* 構造一個新的,空的樹映射,根據給定的比較器排序
*/
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
/**
* 構造了一個包含相同映射的新的樹映射,根據它的鍵的自然順序排列。
*/
public TreeMap(Map<? extends K, ? extends V> m) {
comparator = null;
putAll(m);
}
/**
* 構造一個包含相同映射的新樹映射,並使用指定的排序映射使用相同的排序。
*/
public TreeMap(SortedMap<K, ? extends V> m) {
comparator = m.comparator();
try {
buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
} catch (java.io.IOException cannotHappen) {
} catch (ClassNotFoundException cannotHappen) {
}
}
主要有四種,分爲兩大類型就是採用默認的比較規則和採用自己定義的比較規則,這裏說一下Comparator和Comparable,Comparable是內部實現的,當一個類實現了這個藉口,這個類的對象就能進行排序。而Comparator是外部實現的,可以通過重寫compar方法對排序的規則進行重新定義,比如在Collections.sort()方法中就可以自己重寫這個方法。
一些方法整合
/**
* 返回元素個數
*
* @return the number of key-value mappings in this map
*/
public int size() {
return size;
}
/**
* 如果包含指定的key則返回true
*/
public boolean containsKey(Object key) {
return getEntry(key) != null;
}
/**
* 如果包含一個或者多個value,則返回true,否則返回false
*/
public boolean containsValue(Object value) {
for (Entry<K,V> e = getFirstEntry(); e != null; e = successor(e))//從第一個entry開始比較
if (valEquals(value, e.value))//如果value相等,則返回true
return true;
return false;//返回false
}
/**
* 返回指定的key所映射的value或者,空如果不包含該鍵值對
*/
public V get(Object key) {
Entry<K,V> p = getEntry(key);
return (p==null ? null : p.value);
}
/**
*返回第一個key
*/
public K firstKey() {
return key(getFirstEntry());
}
/**
* 返回最後一個key
*/
public K lastKey() {
return key(getLastEntry());
}
lowerEntry、floorEntry、ceilingEntry 和 higherEntry 的具體實現
/**
* NavigableMap擴展了 SortedMap,具有了針對給定搜索目標返回最接近匹配項的導航方法。
* 方法 lowerEntry、floorEntry、ceilingEntry 和 higherEntry 分別返回與小於、小於等於、
* 大於等於、大於給定鍵的鍵關聯的 Map.Entry 對象,如果不存在這樣的鍵,則返回 null。類似地,
* 方法 lowerKey、floorKey、ceilingKey 和 higherKey 只返回關聯的鍵。所有這些方法是爲查找條目
* 而不是遍歷條目而設計的。
*/
/**
* 返回大於等於最接近key的節點
*/
final Entry<K,V> getCeilingEntry(K key) {
Entry<K,V> p = root;
while (p != null) {//根節點不爲空
int cmp = compare(key, p.key);//比較
if (cmp < 0) {//查找過程,小於,往左找
if (p.left != null)
p = p.left;
else
return p;//找不到節點,但是來到最接近
} else if (cmp > 0) {//大於則往右找
if (p.right != null) {
p = p.right;
} else {//如果不存在右節點,則應該返回到當前節點的父節點的右節點
Entry<K,V> parent = p.parent;
Entry<K,V> ch = p;
while (parent != null && ch == parent.right) {
ch = parent;
parent = parent.parent;
}
return parent;//找不到節點,來到最接近
}
} else
return p;//找到於key相等的節點,直接返回
}
return null;
}
/**
* 找到小於等於最接近key的值,整個過程跟上面的差不多,是一個相反的過程而已
*/
final Entry<K,V> getFloorEntry(K key) {
Entry<K,V> p = root;
while (p != null) {
int cmp = compare(key, p.key);
if (cmp > 0) {
if (p.right != null)
p = p.right;
else
return p;
} else if (cmp < 0) {
if (p.left != null) {
p = p.left;
} else {
Entry<K,V> parent = p.parent;
Entry<K,V> ch = p;
while (parent != null && ch == parent.left) {
ch = parent;
parent = parent.parent;
}
return parent;
}
} else
return p;
}
return null;
}
/**
* 找到大於key的節點,沒有等於操作,
*/
final Entry<K,V> getHigherEntry(K key) {
Entry<K,V> p = root;
while (p != null) {
int cmp = compare(key, p.key);
if (cmp < 0) {
if (p.left != null)
p = p.left;
else
return p;
} else {
if (p.right != null) {
p = p.right;
} else {
Entry<K,V> parent = p.parent;
Entry<K,V> ch = p;
while (parent != null && ch == parent.right) {
ch = parent;
parent = parent.parent;
}
return parent;
}
}
}
return null;
}
/**
* 找到小於key的節點,沒有等於操作
*/
final Entry<K,V> getLowerEntry(K key) {
Entry<K,V> p = root;
while (p != null) {
int cmp = compare(key, p.key);
if (cmp > 0) {
if (p.right != null)
p = p.right;
else
return p;
} else {
if (p.left != null) {
p = p.left;
} else {
Entry<K,V> parent = p.parent;
Entry<K,V> ch = p;
while (parent != null && ch == parent.left) {
ch = parent;
parent = parent.parent;
}
return parent;
}
}
}
return null;
}
具體可以看到這四個方法基本都是按照二叉排序樹的方式進行查找到相應的元素
2.2 重要方法
TreeMap最重要的兩個方法就是put()
和remove()
,因爲這兩個方法涉及到對紅黑樹的增加刪除操作,而且我們知道紅黑樹複雜就在於它的添加節點和刪除幾點操作,所以在介紹着兩個方法之前,我們先來介紹一下,什麼是紅黑樹。
一、概念
R-B Tree,全稱是Red-Black Tree又稱紅黑樹,它是一種特殊的二叉查找樹,紅黑樹的每個節點上都有存儲位表示節點的顏色,可以是紅或黑。
二、特性
1、每個節點或者是紅色,或者是黑色
2、根節點是黑色的
3、每個葉子節點(NIL)是黑色的。注意:這裏的葉子節點,是指爲空的葉子節點
4、如果一個節點是紅色的,則它的子節點必須是黑色的
5、從任意一個節點到其葉子的所有路徑中,所包含的黑節點數量是相同的
特性解析1:根據特性4可知,從每個葉子節點到根節點的所有路徑中不能有兩個連續的紅節點
特性解析2:根據特性5可知,沒有一條路徑會比其它路徑長出兩倍,因而紅黑樹是接近平衡的二叉樹
注:紅黑樹相比較於平衡二叉樹沒有那麼嚴格的要求,它對節點的平衡操作不會比平衡二叉樹更加頻繁,所以紅黑樹經常用在插入刪除比較多的情況下,而平衡二叉樹用在查找比較多的情形下。
三、插入刪除操作
通過上面的介紹我們知道當插入一個節點或者刪除一個節點的時候,會導致5這個條件不成立。所以就要對紅黑樹進行調整,而調整主要有兩部分:
1、變色
2、旋轉
<2.1>旋轉主要有左旋和右旋
左旋:對節點x進行左旋,意味着將“x的右孩子變成x的父親”,而將“x原先的右孩子的左孩子變成x的右孩子”。
即左旋中的“左”是指將別旋轉的節點變成一個左節點
treeMap中左旋源碼
/** 左旋操作
* 對節點x進行左旋,意味着將“x的右孩子變成x的父親”,
* 而將“x原先的右孩子的左孩子變成x的右孩子”。
* 即左旋中的“左”是指將別旋轉的節點變成一個左節點
*/
private void rotateLeft(Entry<K,V> p) {
if (p != null) {
Entry<K,V> r = p.right;//p的右節點
p.right = r.left;//將r的左節點作爲p的右節點
if (r.left != null)//如果r存在左節點
r.left.parent = p;//既將r的左節點鏈接到p的右節點
r.parent = p.parent;//p作爲r的左節點
if (p.parent == null)//p爲根節點
root = r;//r爲根節點
else if (p.parent.left == p)//p存在父節點
p.parent.left = r;//將r作爲p的父節點的左節點
else
p.parent.right = r;//p是父節點的右節點
r.left = p;//r的左節點爲p
p.parent = r;//p的父節點爲r
}
}
右旋:對節點x進行右旋,意味着將“x的左孩子變成x的父親,而將”x原先的左孩子的右孩子變成x的左孩子“。
即右旋中的”右“是指將被旋轉的節點變成一個右節點
treeMap中右旋源碼
/** 右旋操作
*
* 對節點x進行右旋,意味着將“x的左孩子變成x的父親,
* 而將”x原先的左孩子的右孩子變成x的左孩子“。
* 即右旋中的”右“是指將被旋轉的節點變成一個右節點
*/
private void rotateRight(Entry<K,V> p) {
if (p != null) {//p不爲空
Entry<K,V> l = p.left;//p的左節點
p.left = l.right;//將p的左節點置爲l的右節點
if (l.right != null) l.right.parent = p;//p的左節點爲l的右節點關聯起來
l.parent = p.parent; //l替換了原先的p的位置
if (p.parent == null)//p沒有根節點了
root = l;//則l就是根節點
else if (p.parent.right == p)//如果p是父節點的右節點
p.parent.right = l;//則l變成p的父節點的右節點,
else p.parent.left = l;//p爲父節點的左節點
l.right = p;//將父節點連接到它的左節點的右節點
p.parent = l;//p和l的換位置過程
}
}
而在插入和刪除過程中主要有兩種旋轉和一種變色
圖1:兩種旋轉(第一個右旋第二個左旋,可以參照着源碼自己模擬一下)
圖2:一種變色
有了這幾個基礎之後,我們來開始看一下紅黑樹是怎麼樣對插入和刪除進行調整的。
1、插入
(情形一)新節點位於根節點,其沒有父節點時,處理思路:將該節點直接設爲黑色即可
(情形二)新節點的父節點已然是黑色時,處理思路:不用動,這已然是一顆紅黑樹
(情形三)父節點和叔節點都是紅色時,處理思路:a.將父節點和叔節點設爲黑色;b.將祖父節點設爲紅色;c.將祖父節點設爲當前節點,並繼續對新當前節點進行操作
(情形四)父節點是紅色,叔節點是黑色時,又分如下四種情況:
① :當前節點是父親的左孩子,父親是祖父的左孩子(Left-Left),處理思路:a.將祖父節點右旋;b.交換父節點和祖父節點的顏色
②:當前節點是父親的右孩子,父親是祖父的左孩子(Right-Left),處理思路:a.將父節點左旋,並將父節點作爲當前節點; b.然後再使用Left Left情形
③:當前節點是父親的右孩子,父親是祖父的右孩子(Right-Right),處理思路:a.將祖父節點左旋;b.交換父節點和祖父節點的顏色
④:當前節點是父親的左孩子,父親是祖父的右孩子(Left-Right),處理思路:a.將父節點右旋,並將父節點作爲當前節點; b.然後再使用Right Right情形
然後我們來看一下TreeMap裏面的源碼是怎麼做的
put()方法
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // 檢查是否爲空
root = new Entry<>(key, value, null);//空節點
size = 1;//說明一個空key的長度也爲1
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;//獲取比較器
if (cpr != null) {//採用自定義的比較器
do {//根據二叉查找的方式找到插入的位置
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);//如果存在相同的key,則替換
} while (t != null);
}
else {//採用默認的比較器,其過程跟採用自定義的比較器的過程一樣,只是比較規則不一樣
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
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);
}
Entry<K,V> e = new Entry<>(key, value, parent);//不存在則創建一個新的Entry進行插入操作
if (cmp < 0)
parent.left = e; //判斷是插在父節點的左側還是右側
else
parent.right = e;
fixAfterInsertion(e);//插入元素之後的修正
size++;//節點加1
modCount++;//結構性修改
return null;
}
put()
方法主要是找到插入的位置,主要是調用了fixAfterInsertion()
,我們來看看這個方法是怎麼做的。
/**
*
*紅黑樹的插入操作,新插入的節點都爲紅色節點
*/
private void fixAfterInsertion(Entry<K,V> x) {
x.color = RED;
while (x != null && x != root && x.parent.color == RED) {//判斷x不爲空,x不爲根節點,x的父節點爲紅色(如果是黑色就不必調整了)
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {//如果x的父節點是x的祖父節點的左節點
Entry<K,V> y = rightOf(parentOf(parentOf(x)));//y是x祖父的右節點
if (colorOf(y) == RED) {//如果y的顏色是紅色(也就是x的父節點和x的父節點的兄弟節點是紅色的,
//這是第一種情況調整,直接將這兩個變黑,且將祖父節點變紅即可)(---------->情形三)
setColor(parentOf(x), BLACK);//x父節點變黑
setColor(y, BLACK);//x的父節點的兄弟節點變黑
setColor(parentOf(parentOf(x)), RED);//x的祖節點變紅
x = parentOf(parentOf(x));//x來到祖先節點,繼續判斷是否這一層需要改變
} else {//x的祖父的右節點是黑色的(--------------->情形四)
if (x == rightOf(parentOf(x))) {
x = parentOf(x);//x變成其父節點
rotateLeft(x);//左旋
}
setColor(parentOf(x), BLACK);//將x的父節點置爲黑色(------------------->情形二)
setColor(parentOf(parentOf(x)), RED);//x的祖先節點置爲紅色
rotateRight(parentOf(parentOf(x)));//對祖先節點進行右旋
}
} else {//同樣的過程,只不過在祖先節點的右邊進行的操作
Entry<K,V> y = leftOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
if (x == leftOf(parentOf(x))) {
x = parentOf(x);
rotateRight(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateLeft(parentOf(parentOf(x)));
}
}
}
root.color = BLACK;//如果最後操作來到根節點,將根節點變黑(--------------->情形一)
}
可能這樣子看有點抽象,我們用一個栗子來說明它整個過程是怎麼樣做的。
通過插入12 1 9 2 0 11 7 19 4 15 18 5 14 13 10 16 6 3 8 17完成上述所有情形的展示。
(1)插入12
說明:插入的節點若是根節點,則直接將其設置爲黑色
(2)插入1
(3)插入9
(4)插入2
(5)插入0
(6)插入11
(7)插入7
(8)插入19
(9)插入4
(10)插入15
(11)插入18
(12)插入5
(13)插入14
(14)插入13
(15)插入10
(16)插入16
(17)插入6
(18)插入3
(19)插入8
(20)插入17
插入完成。
3.2 刪除操作
remove()
public V remove(Object key) {
Entry<K,V> p = getEntry(key);
if (p == null)
return null;
V oldValue = p.value;
deleteEntry(p);
return oldValue;
}
可以看到remove()主要是調用了deleteEntry的方法,那麼它是怎麼實現的呢。
相比較於插入操作,刪除操作比較複雜,還要判斷刪除節點的孩子,兄弟,父親的節點狀態。下面我們來看一下.
刪除一個節點一般分爲以下兩個部分,首先是判斷要刪除節點類型,將複雜類型轉爲簡單類型。然後開始旋轉調色。
一、首先是找到要刪節點,判斷其孩子類型
情況①:如果X沒有孩子,且如果X是紅色,直接刪除X;如果X是黑色,則以X爲當前節點進行旋轉調色,最後刪掉X
情況②:如果X只有一個孩子C,交換X和C的數值,再對新X進行刪除。根據紅黑樹特性,此時X不可能爲紅色,因爲紅色節點要麼沒有孩子,要麼有兩個黑孩子。此時以新X爲當前節點進行情況①的判斷
情況③:如果X有兩個孩子,則從後繼中找到最小節點D,交換X和D的數值,再對新X進行刪除。此時以新X爲當前節點進行情況①或②的判斷
treeMap源碼
/**
* 刪除一個節點,並且調整
*/
//刪除的節點存在三種情況:
/*
* 1、被刪節點沒有孩子節點
* 2、被刪節點只有一個孩子節點
* 3、被刪節點有左右孩子節點
*
*/
private void deleteEntry(Entry<K,V> p) {
modCount++;//結構性修改
size--;//元素個數減1
// 如果被刪除節點存在左右節點, 找到p中繼遍歷的後續節點
//這個過程就是將第3轉爲第1或者第2種情況
if (p.left != null && p.right != null) {//(------------------------>情形3)
Entry<K,V> s = successor(p);//找到p的後繼節點
//這三步是將p節點的值改爲s節點,但是顏色並沒有改變
p.key = s.key;
p.value = s.value;
p = s;//將p指向s所指節點
}
// 開始調整
Entry<K,V> replacement = (p.left != null ? p.left : p.right);//判斷是否存在左節點,存在則指向左節點,不存在指向右節點
if (replacement != null) {//存在左節點或者右節點(-------------------->情形2)
replacement.parent = p.parent;//將replacement節點作爲p的父節點
//這三步就是用來將p替換掉爲它的孩子節點
if (p.parent == null)//p本身就是根節點
root = replacement;//將根節點置爲replacement
else if (p == p.parent.left)//p是其父節點的左節點
p.parent.left = replacement;//用p的孩子節點替換掉p
else//p是其父節點的右節點,這種情形會存在嗎????
p.parent.right = replacement;//也是一樣
p.left = p.right = p.parent = null;//將p節點刪除
// Fix replacement
if (p.color == BLACK)//如果p是黑色節點,因爲刪除黑色節點後在某條路徑上黑色節點就不平衡,調整
fixAfterDeletion(replacement);//刪除replacement,因爲前面的已經處理好了
} else if (p.parent == null) { // 僅有一個節點,則刪除了,就沒了
root = null;
} else { // 沒有孩子節點.
if (p.color == BLACK)//p是黑色節點(---------------------------->情形1)
fixAfterDeletion(p);//刪除後的修正
if (p.parent != null) {//p的父節點不爲空
if (p == p.parent.left)//p是父節點的左節點
p.parent.left = null;//刪除
else if (p == p.parent.right)//p是父節點的右節點
p.parent.right = null;//刪除
p.parent = null;
}
}
}
二、旋轉調色(N=旋轉調色的當前節點[等於情況①中的X],P=N的父親,W=N的兄弟,Nf=N的遠侄子,Nn=N的近侄子)
情況1:N是根或者N是紅色,則:直接將N設爲黑色
情況2:N不是根且N是黑色,且W爲紅色,則:將W設爲黑色,P設爲紅色,對P進行旋轉(N爲P的左子時進行左旋,N爲P的右子時進行右旋),將情況轉化爲情況1、2、3、4、5
情況3:N不是根且N是黑色,且W爲黑色,且W的左右子均爲黑色,則:將W設爲紅色,將P設爲當前節點進行旋轉調色,將情況轉化爲情況1、2、3、4、5
情況4:N不是根且N是黑色,且W爲黑色,且Nf爲黑色,Nn爲紅色,則:交換W與Nn的顏色,並對W進行旋轉(N爲P的左子進行右旋,N爲P的右子進行左旋),旋轉後N的新兄弟W有一個紅色WR,則轉換爲情況5
情況5:N不是根且N是黑色,且W爲黑色,且Nf爲紅色,Nn爲黑色,則:將W設爲P的顏色,P和Nf設爲黑色,並對P進行旋轉(N爲P的左子進行左旋,N爲P的右子進行右旋),N設爲根
TreeMap源碼
/**
*
* 紅黑樹刪除節點後的調整操作(旋轉調色)
*/
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));//找到兄弟節點
if (colorOf(sib) == RED) {//兄弟節點是紅色,說明父親是黑色(---------------->情況二)
setColor(sib, BLACK);//將兄弟置爲黑色
setColor(parentOf(x), RED);//父親置爲紅色
rotateLeft(parentOf(x));//左旋
sib = rightOf(parentOf(x));//重新指向右節點,且這個節點是黑色的,這個節點現在是調整過的
}
if (colorOf(leftOf(sib)) == BLACK &&
colorOf(rightOf(sib)) == BLACK) {//sib的左右節點均是黑色的(--------------------->情況五)
setColor(sib, RED);//將這個節點變成紅色,變成紅(黑,黑)
x = parentOf(x);//往上走
} else {//兄弟是黑色(-------------------------->情況三)
if (colorOf(rightOf(sib)) == BLACK) {//如果兄弟是黑色的
setColor(leftOf(sib), BLACK);//將其兄弟的左節點設置爲黑色(--------------------->情況四)
setColor(sib, RED);//兄弟設置爲紅色
rotateRight(sib);//右旋
sib = rightOf(parentOf(x));//重新指向右節點,調整過的
}
//變色操作
setColor(sib, colorOf(parentOf(x)));
setColor(parentOf(x), BLACK);
setColor(rightOf(sib), BLACK);
rotateLeft(parentOf(x));
x = root;
}
} else { // x是其父節點的右節點,調整方式差不多
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);//根節點爲黑色(---------------->情況一)
}
我們還是用上面那個栗子,對元素進行刪除。
通過刪除12 1 9 2 0 11 7 19 4 15 18 5 14 13 10 16 6 3 8 17完成上述所有情形的展示。
(1)刪除12
(2)刪除1
(3)刪除9
(4)刪除2
(5)刪除0
(6)刪除11
(7)刪除7
(8)刪除19
(9)刪除4
(10)刪除15
(11)刪除18
(12)刪除5
(13)刪除14
(14)刪除13
(15)刪除10
(16)刪除16
(17)刪除6
(18)刪除3
(19)刪除8
(20)刪除20
刪除完成。
至此,put()
方法和remove()
方法都解析完畢,通過源碼分析我們可以發現,
(1)對於put的方法主要是找到要插入的位置,插入的節點必定爲紅色,且在相對於沒有NIL的根節點上,然後調用fixAfterInsertion()
方法進行調整,調整的過程主要用四種情形。
(2)對於remove方法,主要是要先判斷被刪除節點的孩子節點是否存在,有多少個,如果是左右孩子都存在的,則將其轉換爲第1,2種情形。然後再進行調整刪除。
4、總結
通過TreeMap的源碼我們可以發現,它的主要方法有一個是針對給定搜索目標返回最接近匹配項的導航方法,方法 lowerEntry、floorEntry、ceilingEntry 和 higherEntry 。另外就是插入和刪除操作,這個涉及到紅黑樹的操作,而紅黑樹的操作理解起來也是很難,我通過例子和源碼的解析,希望能更好的理解紅黑樹。其中主要參考別人的解析,對紅黑樹的理解程度還不夠深刻,其中難免有錯誤,還望指正。
此外TreeSet中的結構跟TreeMap是一樣的,只不過TreeSet在插入的過程中判斷了是否存在相同的Key,存在則不插入,不存在則插入
參考資料:
https://blog.csdn.net/u010126792/article/details/62236367 Java集合之NavigableMap與NavigableSet接口
https://my.oschina.net/u/3272058/blog/1914452 紅黑樹原理以及插入、刪除算法 附圖例說明
https://www.cnblogs.com/finite/p/8251587.html HashMap分析之紅黑樹樹化過程