紅黑書的刪除本質上是一個窮舉的過程
刪除情況說明
- 刪除的節點沒有子節點的情況
a) 如果爲紅色,直接刪除即可,不會影響黑色節點的數量
b) 如果爲黑色,刪除的時候需要進行平衡操作 - 刪除的節點只有一個子節點時,刪除節點只能是黑色,子節點也只能是紅色。否則無法滿足紅黑樹黑色節點完全平衡的特性(從任一節點到其每個葉子的所有簡單路徑都包含相同數目的黑色節點)
- 如果刪除節點有兩個子節點時,使用後繼節點作爲替換的刪除節點,然後再按照前面兩種情況處理
刪除情況具體分析
1. 刪除的節點沒有子節點(非null節點)的情況
a、如果爲紅色,直接刪除即可,不會影響黑色節點的數量
刪除紅色節點(13)示例(不影響黑色節點數量,不需要平衡操作)
b、如果爲黑色,刪除的時候需要進行平衡操作
刪除示例,刪除節點11(平衡操作後面會介紹到)
2. 刪除的節點只有一個子節點時,刪除節點只能是黑色,子節點也只能是紅色。
爲什麼該情況下,刪除節點只能是黑色,子節點只能是紅色,如圖所示:
由於該情況下刪除節點的子節點只有一個,並且是紅色,刪除後直接將刪除節點的子節點的顏色變黑,父節點和子節點連接即可。由於刪除的黑色節點被變成黑色的紅色子節點頂上,整體不影響黑色節點的數量,還是符合紅黑樹的特性,所以不需要進行平衡操作。
刪除節點動態示例,刪除節點11(分別是含左子樹和右子樹的情況):
左右
3. 如果刪除節點有兩個子節點時,使用前驅節點或後繼節點作爲替換的刪除節點,然後再按照前面兩種情況處理(本文示例以前驅節點爲例)
找到的前驅節點有兩種情況,分別是前驅節點無子節點的情況和前驅節點只有左子節點的情況.
同時刪除節點和找到的前驅節點的顏色都不固定,所以又區分了好多情況。
下面演示示例只演示其中一種,大概瞭解一下變化規則。具體的平衡邏輯在後面會介紹到。
比如刪除節點100,首先會找到前驅節點60,然後替換內容後,將後繼節點刪除。即將刪除情況轉變成上面介紹到的兩種情況。刪除完成後,再做節點平衡操作(後面會介紹具體平衡細節)。
刪除示例,刪除100節點
紅黑樹刪除平衡情況分析
前面分析的三種情況,1.a 和 2 這兩種情況下刪除比較簡單,不需要做紅黑樹節點平衡操作,而 3 這種情況之前也分析了,通過用前驅節點或者後繼節點替換被刪除的節點,將刪除情況轉換成1、2這種情況然後再刪除,又由於1.a 和 2 不需要進行平衡處理,所以後面刪除平衡操作主要圍繞1.b的情況分析。
首先爲了方便後面的描述,對節點進行一個名稱的約束,如下圖所示:
對於刪除平衡操作也是一個窮舉的過程(刪除黑色節點引起的平衡問題)
分析: 刪除節點的時候,找到前驅節點,並替換兩個節點的內容
- 如果找到要刪除前驅節點含有子節點的情況時,前驅節點一定是黑色,前驅節點的子節點一定是紅色,這種情況就是上述第2種情況,刪除前驅節點,將前驅節點變成黑色,不需要進行平衡操作。
- 如果找到要刪除的前驅節點沒有子節點,並且前驅節點是紅色時。這種情況就是上述1.a 的情況,替換內容後直接刪除即可,由於是紅色節點,不影響黑色節點的數量。所以不需要進行平衡操作。
- 如果找到要刪除的前驅節點沒有子節點,並且前驅節點是黑色時,這種情況就是上述1.b的情況。因爲刪除的的是黑色節點,直接影響到紅黑樹中黑色節點的數量,需要進行數的平衡。這種情況比較複雜,後面內容就只針對這種情況處理。
以下內容是刪除的節點找到的前驅節點是沒有子節點的黑色節點的情況(也就是刪除刪除黑色葉子節點的情況)
- S 是黑色的情況
a、,SL和SR是 NULL 時,刪掉節點4
刪掉節點4後,P變黑色,S變紅色即可完成黑色節點平衡。
b、,SL是紅色,SR是NULL
刪除節點12,因爲刪除的是黑色節點,SL又是紅色的節點,可以利用這個紅色節點補充刪除的黑色節點達到局部黑色節點平衡。
先已SL爲圓心,右旋S節點,更改SL和S的顏色
再已SL爲圓心,左旋P節點,再次更改P和SL節點的顏色即可
c、,SL是null,SR是紅色
類似上面一種場景
刪除節點14後
以S爲圓心,左旋P節點 即可達到平衡操作
d、當P是黑色,SL是null,SR是null
由於刪除的是一個黑色節點,並且相鄰兄弟節點沒有紅色節點
所以在刪除後左側這一塊是不可能平衡,就會影響到叔叔節點的顏色
d、當P是黑色,SL是紅色,SR是null
刪除節點16 後,已SL爲圓心,右旋S 節點,SL和S均變色
然後再以SL爲原型左旋P節點 最終三個節點的顏色都變成黑色
==d、當P是黑色,SL是null,SR是null紅色
同上面類似,以S爲圓心,左旋P節點 最後都紅色節點變成黑色節點即可
- S 是紅色的情況
和上面類似
具體變化細節可以用下面的網站工具實踐:
http://www.u396.com/wp-content/collection/data-structure-visualizations/RedBlack.html
保持平衡的原則就是節點的每個分支的黑色節點數是相同的
HashMap紅黑樹刪除源碼分析
remove方法
/**
* 從HashMap中刪除掉指定key對應的鍵值對,並返回被刪除的鍵值對的值
* 如果返回空,說明key可能不存在,也可能key對應的值就是null
* 如果想確定到底key是否存在可以使用containsKey方法
*/
public V remove(Object key) {
Node<K,V> e; // 定義節點變量,用來存儲要被刪除的節點(鍵值對)
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value; // 調用removeNode方法
}
removeNode 方法
final Node<K,V> removeNode(int hash, Object key, Object value,boolean matchValue, boolean movable) {
/**
* tab 當前當前結點數組
* hash 對應的節點
* n 數組長度
* index 節點索引,通過hash 和 n計算得到
*/
Node<K,V>[] tab; Node<K,V> p; int n, index;
// 根據hash找到 index位置的節點,即樹的根節點或者鏈表的首節點
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node<K,V> node = null, e; K k; V v;
// 首節點或者根節點是當前key對應的節點的情況
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
else if ((e = p.next) != null) { // 首節點或者根節點不是當前要刪除對象的情況
// 樹結構的處理邏輯
if (p instanceof TreeNode)
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else { // 鏈表結構 遍歷找到鏈表中當前key 對應的node節點
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
break;
}
p = e;
} while ((e = e.next) != null);
}
}
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
if (node instanceof TreeNode)
// 樹節點的刪除邏輯
// 窮舉刪除節點以及紅黑書的平衡邏輯
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
else if (node == p) // 鏈表結構的情況,並且是首節點
tab[index] = node.next; //直接將數據位置指向下一個節點即可
else //不是首節點的情況,將p的下一個節點指向要刪除的下一個節點
p.next = node.next;
++modCount; //修改次數+1
--size; //size -1
afterNodeRemoval(node);
return node; // 返回要刪除的node節點
}
}
return null;
}