TreeMap紅黑樹原始碼詳解
尊重原創,轉載請標明出處 http://blog.csdn.net/abcdef314159
在分析原始碼之前,最好要標註出處,因為在Java中和Android中同一個類可能程式碼就會不一樣,甚至在Android中不同版本之間程式碼也可能會有很大的差別,下面分析的是紅黑樹TreeMap,在\sources\android-25中。
紅黑樹的幾個性質要先說一下,
1. 每個節點是紅色或者黑色的。
2. 根節點是黑色的。
3. 每個葉節點的子節點是黑色的(葉子節點的子節點可以認為是null的)。
4. 如果一個節點是紅色的,則它的左右子節點都必須是黑色的。
5. 對任意一個節點來說,從它到葉節點的所有路徑必須包含相同數目的黑色節點。
TreeMap還有一個性質,就是他的左子樹比他小,右子樹比他大,這裡的比較是按照key排序的。存放的時候如果key一樣就把他替換了。
乍一看程式碼TreeMap有3000多行,其實他裡面有很多內部類,有Values,EntrySet,KeySet,PrivateEntryIterator,EntryIterator,ValueIterator,KeyIterator,DescendingKeyIterator,
NavigableSubMap,AscendingSubMap,DescendingSubMap,SubMap,TreeMapEntry,TreeMapSpliterator,KeySpliterator,DescendingKeySpliterator,ValueSpliterator,
EntrySpliterator多達十幾個內部類。其實很多都不需要了解,下面主要來看一下TreeMapEntry這個類,它主要是紅黑樹的節點
既然是棵樹,那麼肯定就會有put方法以及remove方法,那麼這裡就從最簡單的著手,先看一下put方法/** * Node in the Tree. Doubles as a means to pass key-value pairs back to * user (see Map.Entry). */ static final class TreeMapEntry<K,V> implements Map.Entry<K,V> { K key; V value; TreeMapEntry<K,V> left = null;//左子樹 TreeMapEntry<K,V> right = null;//右子樹 TreeMapEntry<K,V> parent;//父節點 boolean color = BLACK;//預設為黑色 /** * Make a new cell with given key, value, and parent, and with * {@code null} child links, and BLACK color. */ TreeMapEntry(K key, V value, TreeMapEntry<K,V> parent) { this.key = key; this.value = value; this.parent = parent; } /** * Returns the key. * * @return the key */ public K getKey() { return key; } /** * Returns the value associated with the key. * * @return the value associated with the key */ public V getValue() { return value; } /** * Replaces the value currently associated with the key with the given * value. * * @return the value associated with the key before this method was * called */ public V setValue(V value) { V oldValue = this.value; this.value = value; return oldValue; } public boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry<?,?> e = (Map.Entry<?,?>)o; return valEquals(key,e.getKey()) && valEquals(value,e.getValue()); } public int hashCode() { int keyHash = (key==null ? 0 : key.hashCode()); int valueHash = (value==null ? 0 : value.hashCode()); return keyHash ^ valueHash; } public String toString() { return key + "=" + value; } }
/**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for the key, the old
* value is replaced.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
*
* @return the previous value associated with {@code key}, or
* {@code null} if there was no mapping for {@code key}.
* (A {@code null} return can also indicate that the map
* previously associated {@code null} with {@code key}.)
* @throws ClassCastException if the specified key cannot be compared
* with the keys currently in the map
* @throws NullPointerException if the specified key is null
* and this map uses natural ordering, or its comparator
* does not permit null keys
*/
//註釋說的很明白,如果有相同的key,那麼之前老的就會被代替,
public V put(K key, V value) {
TreeMapEntry<K,V> t = root;
if (t == null) {
// We could just call compare(key, key) for its side effect of checking the type and
// nullness of the input key. However, several applications seem to have written comparators
// that only expect to be called on elements that aren't equal to each other (after
// making assumptions about the domain of the map). Clearly, such comparators are bogus
// because get() would never work, but TreeSets are frequently used for sorting a set
// of distinct elements.
//
// As a temporary work around, we perform the null & instanceof checks by hand so that
// we can guarantee that elements are never compared against themselves.
//
// compare(key, key);
//
// **** THIS CHANGE WILL BE REVERTED IN A FUTURE ANDROID RELEASE ****
// key檢查,如果comparator為null,那麼key是不能為null的,並且key是實現Comparable介面的,因為TreeMaori是有序的,需要比較.
//如果comparator不為null,則需要驗證,comparator是自己傳進來的,根據key == null,comparator.compare(key, key)是否可執行,
//還是拋異常,這個是由你自己寫的compare方法決定的。
if (comparator != null) {
if (key == null) {
comparator.compare(key, key);
}
} else {//如果沒有傳入comparator,則key不能為null,且必須實現Comparable介面
if (key == null) {
throw new NullPointerException("key == null");
} else if (!(key instanceof Comparable)) {
throw new ClassCastException(
"Cannot cast" + key.getClass().getName() + " to Comparable.");
}
}
//建立root,這個是在上面root為null的時候才走到這一步
root = new TreeMapEntry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
TreeMapEntry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
//comparator無論是等於null還是不等於null,原理都是基本差不多
if (cpr != null) {
do {//新增的時候如果原來有就替換,如果沒有就不斷的迴圈,這裡注意TreeMap的左子樹比他小,右子樹比他大,這裡比較的是key
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);//如果正好找到了就把它替換掉
} 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);
}
TreeMapEntry<K,V> e = new TreeMapEntry<>(key, value, parent);//建立一個節點
if (cmp < 0)//節點比他父節點小,放到左子樹
parent.left = e;
else
parent.right = e;//節點比他父節點大,放到右子樹
fixAfterInsertion(e);
//這裡是關鍵,樹的調整,因為上面建立節點的時候預設的是一棵黑色的數,而樹原來是平衡的,加入節點之後導致數的不平衡,所以需要調節
size++;
modCount++;
return null;
}
put方法存放的時候,首先是會存放到葉子節點,然後在進行調整。上面有一個重量級的方法fixAfterInsertion還沒有分析,在分析fixAfterInsertion方法之前來看一下其他的幾個方法,
private static <K,V> boolean colorOf(TreeMapEntry<K,V> p) {
//獲取樹的顏色,如果為null,則為黑色,這一點要記住,待會下面分析的時候會用到
return (p == null ? BLACK : p.color);
}
//找父節點
private static <K,V> TreeMapEntry<K,V> parentOf(TreeMapEntry<K,V> p) {
return (p == null ? null: p.parent);
}
//設定節點顏色
private static <K,V> void setColor(TreeMapEntry<K,V> p, boolean c) {
if (p != null)
p.color = c;
}
//獲取左子樹
private static <K,V> TreeMapEntry<K,V> leftOf(TreeMapEntry<K,V> p) {
return (p == null) ? null: p.left;
}
//獲取右子樹
private static <K,V> TreeMapEntry<K,V> rightOf(TreeMapEntry<K,V> p) {
return (p == null) ? null: p.right;
}
下面再來看一下fixAfterInsertion方法
/** From CLR */
private void fixAfterInsertion(TreeMapEntry<K,V> x) {
//在紅黑樹裡面,如果加入一個黑色節點,則導致所有經過這個節點的路徑黑色節點數量增加1,
//這樣就肯定破壞了紅黑樹中到所有葉節點經過的黑色節點數量一樣的約定。所以,
//我們最簡單的辦法是先設定加入的節點是紅色的。
x.color = RED;
//當前節點變為紅色,如果他的父節點是紅色則需要調整,因為父節點和子節點不能同時為紅色,但可以同時為黑色,
//所以這裡的迴圈條件是父節點必須為紅色才需要調整。
while (x != null && x != root && x.parent.color == RED) {
//這裡會分多鐘情況討論,
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {//如果父節點是爺爺節點的左節點
TreeMapEntry<K,V> y = rightOf(parentOf(parentOf(x)));//當前節點的叔叔節點,也就是他父節點的兄弟節點,這個也可能為null
if (colorOf(y) == RED) {
//因為新增的是紅色,而父與子不能同時為紅色,所以打破了平衡,需要先讓父為黑色,然後再讓爺爺為紅色,因為爺爺節點為紅色,所以
//子節點必須為黑色,所以把叔叔節點也調為黑色,繼續往上調整,
//(1)如果當前節點的叔叔節點是紅色,也就是說他的叔叔節點一定是存在的,因為如果為null,則colorOf會返回黑色。既然叔叔節點
//是紅色,那麼他的爺爺節點一定是黑色,否則就打破了紅黑平衡,那麼他的父節點也一定是紅色,因為只有父節點為紅色才執行while
//迴圈,這種情況下,無論x是父節點的左節點還是右節點都不需要在旋轉,
setColor(parentOf(x), BLACK);//讓x的父節點為黑色
setColor(y, BLACK);//叔叔節點也設為黑色
setColor(parentOf(parentOf(x)), RED);//當前節點的爺爺節點為紅色
//把爺爺節點設定為紅色之後,繼續往上迴圈,即使執行到最上端也不用擔心,因為在最後會把根節點設定為黑色的。
x = parentOf(parentOf(x));
} else {
//如果他的叔叔節點是黑色的,並且他的父節點是紅色的,那麼說明他的叔叔節點是null,因為如果叔叔節點是黑色的且不為空,
//那麼違反了他的第5條性質所以這裡叔叔節點是空。因為叔叔節點
//為空,出現了不平衡,所以這裡當前節點無論是父節點的左節點還是右節點,都需要旋轉
if (x == rightOf(parentOf(x))) {
//(2)當前節點是父節點的右節點,
x = parentOf(x);//讓當前節點的父節點為當前節點
rotateLeft(x);//對父節點進行左旋
}
//(3)當前節點是父節點的左節點,這個左節點有可能是新增的時候新增到左節點的,也有可能是上面旋轉的時候旋轉到左節點的
setColor(parentOf(x), BLACK);//讓父節點為黑色
setColor(parentOf(parentOf(x)), RED);//爺爺節點變為紅色
rotateRight(parentOf(parentOf(x)));//對爺爺節點右旋
}
} else {//父節點為爺爺節點的右節點
TreeMapEntry<K,V> y = leftOf(parentOf(parentOf(x)));//找出叔叔節點
//如果叔叔節點是紅色,那麼說明他一定是存在的,所以不需要旋轉,這裡要銘記,無論是左旋還是右旋的前提條件是他的叔叔節點不存在,
//如果存在就不需要旋轉,只需要遍歷改變顏色值即可
if (colorOf(y) == RED) {
//(4)修改顏色
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);//(5)右旋操作
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateLeft(parentOf(parentOf(x)));//(6)左旋操作
}
}
}
root.color = BLACK;//根節點必須是黑色
}
上面列出了6中可能,下面通過6張圖來說明
下面是圖1,不需要旋轉,只需要調整顏色即可
下面是圖2和圖3,因為不平衡,所以需要旋轉
下面是圖4,和圖1差不多,也分兩種情況,一種是左節點一種是右節點
下面是圖5和圖6,因為不平衡,所以需要旋轉
無論怎麼旋轉,他的左節點永遠小於他,右節點永遠大於他。通過不斷的while迴圈,最終保證紅黑樹的平衡。下面來看一下旋轉的方法,先看一下圖
/** From CLR */
private void rotateLeft(TreeMapEntry<K,V> p) {
//參照上面旋轉的圖來分析,p就是圖中的x
if (p != null) {
TreeMapEntry<K,V> r = p.right;//r相當於圖中的40節點
p.right = r.left;//讓35節點(r.left也就是圖中40節點的左節點)等於p的右節點,看上圖
//如果r.left!=null,讓p等於他的父節點,因為在上一步他已經等於p的右節點,自然就是他的子節點
//所以他的父節點自然就變成p了
if (r.left != null)
r.left.parent = p;
//讓原來p節點的父節點等於r的父節點,可以根據圖看的很明白,通過旋轉40節點,挪到上面了,
r.parent = p.parent;
//這裡也很好理解,如果原來p的父節點為null,說明原來父節點就是根節點,這裡讓調整過來的r節點
//(即40節點)成為根節點
if (p.parent == null)
root = r;
//這裡很好理解,如果原來p節點是左節點就讓調整過來的r節點變成左節點,是右節點就讓r變成右節點
else if (p.parent.left == p)
p.parent.left = r;
else
p.parent.right = r;
//讓p(也就是圖中的30節點)成為r(也就是圖中的40節點)的左節點,
r.left = p;
//然後讓r(圖中的40節點)成為p(圖中的30節點)的父節點。
p.parent = r;
}
}
而右旋方法rotateRight和左旋差不多,這裡就不在分析。put方法分析完了,那麼下一個就是remove方法了,
public V remove(Object key) {
//getEntry(Object key)方法是獲取TreeMapEntry,如果比當前節點大則找右節點,如果比當前節點小則找左節點
//通過不斷的迴圈,知道找到為止,如果沒找著則返回為null。
TreeMapEntry<K,V> p = getEntry(key);
if (p == null)
return null;
V oldValue = p.value;
//找到之後刪除
deleteEntry(p);
return oldValue;
}
下面再看一下刪除方法deleteEntry。
/**
* Delete node p, and then rebalance the tree.
*/
private void deleteEntry(TreeMapEntry<K,V> p) {
modCount++;
size--;//刪除,size減1
// If strictly internal, copy successor's element to p and then make p
// point to successor.
//當有兩個節點的時候不能直接刪除,要刪除他的後繼節點,後繼節點最多隻有一個子節點。因為如果p有兩個子節點,你刪除之後
//他的兩個子節點怎麼掛載,掛載到p的父節點下?這顯然不合理,因為這樣一來p的父節點很有可能會有3個子節點,那麼最好的辦法
//就是找一個替罪羊,刪除p的後繼節點s,當然刪除前需要把後繼節點s的值賦給p
if (p.left != null && p.right != null) {
//successor(p)返回p節點的後繼節點,其實這個後繼節點就是比p大的最小值,這個待會再分析
TreeMapEntry<K,V> s = successor(p);
//把後繼節點s的值賦值給p,待會刪除的是後繼節點s,注意這裡賦值並沒有把顏色賦給原來的p。當然這裡刪除並不會打亂樹的
//大小順序,因為後繼節點是比p大的最小值,賦值之後在刪除,樹的大小順序依然是正確的,這裡只是把s的值賦給了p,如果
//再把p原來的值賦給s,在刪除s可能就會更好理解了,但這其實並不影響。
p.key = s.key;
p.value = s.value;
p = s;
} // p has 2 children
// Start fixup at replacement node, if it exists.
TreeMapEntry<K,V> replacement = (p.left != null ? p.left : p.right);
if (replacement != null) {
//p有子節點,並且有且只有一個節點,因為如果p有兩個節點,那麼上面的successor方法會一直查詢,要麼返回p的右節點
//(前提是p的右節點沒有左節點),要麼會一直迴圈找到p的右節點的最左孫子節點。待會看successor程式碼會發現,如果p
//有2個子節點,那麼successor返回的節點最多也只有1個節點。
// Link replacement to parent
replacement.parent = p.parent;
//如果p的父節點為null,說明p是root節點,因為執行到這一步,所以replacement是p唯一的節點,把p節點刪除後,讓
//replacement成為root節點
if (p.parent == null)
root = replacement;
//這個不會變,原來p是左節點就讓replacement成為左節點,原來p為右節點就讓replacement成為右節點。相當於替換p節點的位置
else if (p == p.parent.left)
p.parent.left = replacement;
else
p.parent.right = replacement;
// Null out links so they are OK to use by fixAfterDeletion.
//把p的子節點及父節點全部斷開
p.left = p.right = p.parent = null;
// Fix replacement
//如果刪除的是黑色要進行調整,因為黑色刪除會打破紅黑平衡,
//所以這裡只是做顏色調整,調整的時候並沒有刪除。
if (p.color == BLACK)
//上面的p確定只有一個節點replacement,但這裡replacement子節點是不確定的,有可能0個,1個或2個。
fixAfterDeletion(replacement);
} else if (p.parent == null) { // return if we are the only node.
root = null;//p是根節點,直接刪除,不用調整
} else { // No children. Use self as phantom replacement and unlink.
//p沒有子節點,說明p是個葉子節點,不需要找繼承者,調整完之後直接刪除就可以了。
//如果刪除的是黑色,需要調整,上面的調整是刪除之後再調整,是因為刪除的不是葉子節點,如果調整之後再刪除還有可能出現錯誤,
//而這裡是調整之後再刪除,是因為這裡刪除的是葉子節點,調整完之後直接把葉子節點刪除就是了,刪除之後調整的是顏色,並不是樹的
//大小順序
if (p.color == BLACK)
fixAfterDeletion(p);
//調整完之後再刪除p節點,此時p是葉子節點,因為調整完之後通過左旋或右旋p.rarent可能為null,所以這裡需要判斷
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;
}
}
}
上面分析的時候有兩個方法successor和fixAfterDeletion沒有分析,下面先來看一下successor方法,這個方法很簡單,其實就是返回大於節點p的最小值,看一下程式碼
/**
* Returns the successor of the specified Entry, or null if no such.
*/
static <K,V> TreeMapEntry<K,V> successor(TreeMapEntry<K,V> t) {
if (t == null)
return null;
else if (t.right != null) {//t的右節點不為空
TreeMapEntry<K,V> p = t.right;
//迴圈左節點,如果左節點一開始就為null,那麼直接就返回p,此時p是t的右節點,如果p的左節點
//存在,那麼會一直迴圈,一直在找左節點,直到為null為止,
while (p.left != null)
p = p.left;
return p;//所以查詢到最後,返回的p最多隻有一個節點,並且查詢的p是大於t的最小值
} else {
TreeMapEntry<K,V> p = t.parent;
TreeMapEntry<K,V> ch = t;
//不停的往上找父節點,直到p為null,或者父節點(這個父節點也可能是父節點的父節點的父節點,反正
//只要滿足條件就會一直往上迴圈)是左節點,最終查詢的結果是p是大於t的最小值,要明白這一點,首先要
//明白,一個節點大於他的左節點小於他的右節點
while (p != null && ch == p.right) {
ch = p;
p = p.parent;
}
return p;//這裡返回的p有可能有2個子節點,並且只有在t沒有右節點的時候才有可能。
}
}
OK,下面再來看一下fixAfterDeletion方法,因為x所在分支少了一個黑色的節點,所以他的主要目的就是讓x分支增加一個黑色節點。這個比fixAfterInsertion方法還難理解,看程式碼
/** From CLR */
private void fixAfterDeletion(TreeMapEntry<K,V> x) {
//再看這個方法之前先看一下最後一行程式碼,他會把x節點設定為黑色
//很明顯,在x只有黑色的時候才會調整,因為刪除黑色打破了紅黑平衡,但deleteEntry方法中的刪除有兩種,
//一種是替換之後的replacement,這個replacement不是刪除的節點,需要刪除的節點在這之前就已經被刪除,
//他是來找平衡的,因為刪除之後在這一分支上少了一個黑色節點,如果replacement節點為紅色,那麼不用執行
// while迴圈,直接在最後把它置為黑色就正好彌補了刪除的黑色節點,如果replacement是黑色,那麼需要執行
//下面的while迴圈(前提是replacement不等於root)。還一種就是沒有子節點的,先調整完在刪除,如果他是
//紅色,就不用執行while迴圈,直接刪除就是了,下面置不置為黑色都一樣,如果是黑色,就必須執行下面的方法,
//因為刪除黑色會打破紅黑平衡。
while (x != root && colorOf(x) == BLACK) {
//x是父節點的左節點
if (x == leftOf(parentOf(x))) {
TreeMapEntry<K,V> sib = rightOf(parentOf(x));//x的兄弟節點
//(1)兄弟節點是紅色,這種情況下兄弟節點的父節點和子節點都一定是黑色的,
//然後讓兄弟節點變為黑色,父節點變為紅色,這種情況下從root節點到兄弟節點的各葉子節點黑色個數沒有變化,
//但從root節點到x節點的黑色個數少了1(如果刪除的是黑色節點,那麼傳進來的replacement分支上其實就已經少
//了一個黑色,但這裡減少只是相對於傳進來的x來說的,是相對的。),然後通過左旋,達到各分支上的黑色
//節點一致。
if (colorOf(sib) == RED) {
setColor(sib, BLACK);
setColor(parentOf(x), RED);
rotateLeft(parentOf(x));//左旋
//通過左旋,x的位置已經改變,但這裡sib還是等於x的兄弟節點。
sib = rightOf(parentOf(x));
}
//其實執行到這一步往後可以認為x所在分支少了一個黑色節點。並且兄弟節點sib是黑色的
//(2)首先可以肯定一點的是sib節點肯定是黑色的,通過(1)及上面程式碼可以明白,如果sib是紅色,那麼他的子節
//點是黑色的,經過上面的左旋調整,sib的子節點會變為sib,也就是黑色。這裡如果sib的兩個子節點都是黑色的,那麼
//讓sib為紅色,這樣一來x分支和他兄弟分支sib都相當於少了一個黑色節點,所以從root節點到x分支和到sib分支的黑色
//節點都是一樣的。那麼問題來了,從root節點到x和sib分支的黑色節點比到其他分支的黑色節點明顯是少了一個黑色節點,
//但是後面又讓x等於x的父節點,所以如果父節點為紅色,則跳出迴圈,在最後再變為黑色,此時所有的節點都又達到平衡,
//如果為黑色,則繼續迴圈。
if (colorOf(leftOf(sib)) == BLACK &&
colorOf(rightOf(sib)) == BLACK) {
setColor(sib, RED);
x = parentOf(x);
} else {
//(3)如果sib的右子節點是黑色,左子節點是紅色(如果兩個都是黑色則執行上面),這樣不能直接讓sib節點變為紅色,因為
//這樣會打破平衡.這個時候需要讓左子節點變黑色,sib節點再變為紅色。如果這樣,那麼問題就來了,因為這樣從root到
//sib左分支的黑色節點是沒有變化,但從root到sib右分支的黑色節點明顯是少了一個黑色節點,然後再對sib進行右旋,
//讓sib的左右子節點又各自達到平衡。然後在重新定位sib節點。但即使這樣,從root到x節點的分支依然少了一個黑色節點。
if (colorOf(rightOf(sib)) == BLACK) {
setColor(leftOf(sib), BLACK);
setColor(sib, RED);
rotateRight(sib);
sib = rightOf(parentOf(x));
}
//(4)由上上面可知sib是黑色的,即使sib的右節點為黑色,通過上面的改變顏色及旋轉到最後sib還是黑色。sib的右節點是紅色,
//如果是黑色,那麼執行上面也會變為黑色,可以看一下下面的圖(3).執行到這一步,從root
//到sib分支的黑色節點是沒有變化,但從root到x分支的黑色節點是少了一個,然後執行下面的程式碼會使x的兄弟分支黑色節點不變
//x分支黑色節點加1,最終達到平衡。然後讓x等於root,退出迴圈。最後是對root置為黑色,基本沒有影響(因為root節點
//本來就是黑色),這裡的程式碼讓sib的顏色等於x父節點的顏色,基本沒影響,其實他最終目的是讓x所在分支增加一個黑色節點,
//來達到紅黑平衡。
setColor(sib, colorOf(parentOf(x)));
setColor(parentOf(x), BLACK);
setColor(rightOf(sib), BLACK);
rotateLeft(parentOf(x));
x = root;
}
} else { // symmetric
//對稱的,x是父節點的右節點的情況。
TreeMapEntry<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);//最後把x節點置為黑色
}
結合上面程式碼看一下下面的四張圖
OK,到現在為止put和move方法都已經分析完了,下面看一下其他方法,
/**
* Returns the first Entry in the TreeMap (according to the TreeMap's
* key-sort function). Returns null if the TreeMap is empty.
*/
//返回第一個節點,最左邊的,也是最小值
final TreeMapEntry<K,V> getFirstEntry() {
TreeMapEntry<K,V> p = root;
if (p != null)
while (p.left != null)
p = p.left;
return p;
}
/**
* Returns the last Entry in the TreeMap (according to the TreeMap's
* key-sort function). Returns null if the TreeMap is empty.
*/
//返回最後一個節點,最右邊的,也是最大值
final TreeMapEntry<K,V> getLastEntry() {
TreeMapEntry<K,V> p = root;
if (p != null)
while (p.right != null)
p = p.right;
return p;
}
再來看一下containsValue方法
//通過不停的迴圈查詢,先從第一個查詢,getFirstEntry()返回的是樹的最小值,如果不等,再找比e大的最小值
//successor(e)返回的是e的後繼節點,其實就是比e大的最小值,他還可以用於輸出排序的大小
public boolean containsValue(Object value) {
for (TreeMapEntry<K,V> e = getFirstEntry(); e != null; e = successor(e))
if (valEquals(value, e.value))
return true;
return false;
}
再來看一個getCeilingEntry,這個方法比較繞
/**
* Gets the entry corresponding to the specified key; if no such entry
* exists, returns the entry for the least key greater than the specified
* key; if no such entry exists (i.e., the greatest key in the Tree is less
* than the specified key), returns {@code null}.
*/
//返回最小key大於或等於指定key的節點。
final TreeMapEntry<K,V> getCeilingEntry(K key) {
TreeMapEntry<K,V> p = root;
while (p != null) {
int cmp = compare(key, p.key);
//指定key的節點小於查詢的p節點,如果p的左節點(左節點比p小)存在就繼續迴圈,如果不存在就返回p
if (cmp < 0) {
if (p.left != null)
p = p.left;
else
return p;//p節點比key的節點大
} else if (cmp > 0) {//指定的節點大於p節點
if (p.right != null) {//說明key節點比p節點大,如果p的右節點存在,就繼續迴圈,查詢更大的
p = p.right;
} else {
//p沒有右節點,因為p的左節點是小於p的,既然查詢的比p大,所以就往上找p的父節點,因為父節點也比右子節點小,所以要查詢到
//父節點是父父節點的左節點為止,這個和查詢後繼節點其實類似,下面停止迴圈的條件要麼是parent為null,要麼ch是父節點的左節點,
//這個可能比較繞,我們先記下面迴圈停止的父節點是father節點(下面停止的條件是下一個迴圈的節點是father節點的左節點),在上
//面的迴圈中,能走到father的左節點這條路線,說明key的節點是小於father節點的,而沿著father節點的左分支一直找下去也沒找到大
//於key的節點,這說明father的左節點都是小於key的,所以最後只能網上查詢,找到father節點返回。
TreeMapEntry<K,V> parent = p.parent;
TreeMapEntry<K,V> ch = p;
while (parent != null && ch == parent.right) {//如果不為null,是左節點的時候停止迴圈
ch = parent;
parent = parent.parent;
}
return parent;
}
} else
return p;//如果存在直接返回
}
return null;
}
下面再來看一個和getCeilingEntry方法類似的方法getFloorEntry。
/**
* Gets the entry corresponding to the specified key; if no such entry
* exists, returns the entry for the greatest key less than the specified
* key; if no such entry exists, returns {@code null}.
*/
//返回最大key小於或等於指定key的節點。
final TreeMapEntry<K,V> getFloorEntry(K key) {
TreeMapEntry<K,V> p = root;
while (p != null) {
int cmp = compare(key, p.key);
if (cmp > 0) {//指定的key大於查詢的p,就是查詢的p小了
if (p.right != null)//如果存在就迴圈,查詢最大的
p = p.right;
else
return p;//否則就返回p,這個p是小於key的
} else if (cmp < 0) {//說明查詢的p大於指定的key,就是查詢的p大了
if (p.left != null) {//既然大了,那就往小的找
p = p.left;
} else {
TreeMapEntry<K,V> parent = p.parent;
TreeMapEntry<K,V> ch = p;
//往上找,停止的條件是parent為null,或者ch是父節點的右節點,其實這個方法和getCeilingEntry方法
//非常相似,能走到這一步說明他的父節點比key的大,所以才往左走,當他沒有左節點的時候,說明沒有
//找到比他小的,但是父節點是比他大的不合適,所以一直往上查詢,當查到父節點是父父節點的右節點的
//時候返回,我們暫時記這個父父節點為father,當沿著father的右節點查詢的時候,說明key是比father的
//右節點大的,當沿著father的左節點查詢的時候說明是要查詢比key的小的,但直到最後也沒找到的時候,說明
//father的右分支都是都是比key的大,注意只好往上查詢,找到father節點,因為father節點是比key的小。
while (parent != null && ch == parent.left) {
ch = parent;
parent = parent.parent;
}
return parent;
}
} else
return p;//正好找到,直接返回
}
return null;
}
getHigherEntry函式和getCeilingEntry函式有點類似,不同點是如果有相同的key,getCeilingEntry會直接返回,而getHigherEntry仍然會返回比key大的最小節點,
同理getLowerEntry函式和getFloorEntry函式很相似,這裡就不在詳述。下面在看一個方法predecessor
/**
* Returns the predecessor of the specified Entry, or null if no such.
*/
static <K,V> TreeMapEntry<K,V> predecessor(TreeMapEntry<K,V> t) {
//這個和successor相反,他返回的是前繼節點。後繼節點返回的是大於t的最小節點,而前繼節點返回的是小於
// t的最大節點
if (t == null)
return null;
else if (t.left != null) {//查詢t的左節點是最右節點,其實也就是返回小於t的最大值
TreeMapEntry<K,V> p = t.left;
while (p.right != null)
p = p.right;
return p;
} else {
//如果t沒有左左子節點,則只能往上找了,因為右節點是大於的,所以不合適,那麼往上找也是大於的,那麼就只有一個
//找到父節點是父父節點是右節點,返回這個父父節點就行了,這個如果不好理解可以看一下getFloorEntry函式的註釋。
//因為一個節點的右節點及右節點的子節點都是大於當前節點的,所以當往左沒有找到的時候就往上找,直到找到一個節點是
//父節點的右節點的時候,這個父節點就是小於t的最大節點,這時返回父節點。
TreeMapEntry<K,V> p = t.parent;
TreeMapEntry<K,V> ch = t;
while (p != null && ch == p.left) {
ch = p;
p = p.parent;
}
return p;
}
}
OK,目前為止TreeMap主要方法都已整理完畢。
參閱:Java 集合系列12之 TreeMap詳細介紹(原始碼解析)和使用示例