對於紅黑樹的刪除,看了數據結構的書,也看了很多網上的講解和實現,但都不滿意。很多講解都是囫圇吞棗,知其然,不知其所以然,講的晦澀難懂。
紅黑樹是平衡二叉樹的一種,其刪除算法是比較復雜的,因為刪除后還要保持紅黑樹的特性。紅黑樹的特性如下:
-
- 節點是紅色或黑色。
- 根是黑色。
- 所有葉子都是黑色(葉子是NIL節點)。
- 每個紅色節點必須有兩個黑色的子節點。(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點。)
- 從任一節點到其每個葉子的所有簡單路徑都包含相同數目的黑色節點(簡稱黑高)。
因此,從紅黑樹最基礎的特性出發,拋開教科書和網上的算法,畫了無數張圖,分析了多種可能的情況以后,經過歸納提煉,實現了不同於教科書上的刪除算法。
經過多次畫圖證明以后,筆者發現,紅黑樹的刪除算法不是唯一的,不管如何調整,只要保證刪除后還是一顆紅黑樹即可。
因此,筆者實現的 刪除思路和算法如下:
1. 刪除轉移:(這部分是大路貨,不是自己實現的)
- 如果被刪除節點有兩個非空子節點,則用后繼節點的值代替該節點的值,這樣演變成了刪除后繼節點;否則轉下一條;
- 如果被刪除節點一個或兩個孩子都為空:若有非空孩子,則用非空孩子節點替代之;若無,直接刪除;
- 刪除后繼節點:后繼節點的左孩子節點一定為空,右孩子可能為空;處理如上一條;
刪除轉移的目的是為了簡化刪除操作,更是為了簡化修復操作。因為刪除轉移后,最終待刪除的節點最多只會有一個非空孩子。
2. 刪除后修復:
2.1 簡單的情況:
-
- 若被刪除節點為紅色節點,不需修復;此時該節點一定為紅色的葉子節點(可根據紅黑樹的特性證明);
- 若被刪除的節點為黑色節點,且有一個非空子節點,則將其非空子節點顏色塗黑即可;
對於以上兩種簡單的情況,做個說明:根據紅黑樹特性,非空子節點一定為紅色節點,否則將違反特性;根據紅黑樹特性,在刪除前,一顆紅黑樹不可能出現以下幾種情況:
(圖片來自網絡,感謝原作者。)
2.2 復雜的情況:刪除后需要修復的。
只有當被刪除的節點為黑色葉子節點時,導致該節點所在的分支,少了一個黑色節點,樹不再平衡,因此需要修復。
修復的整體思路是:
-
- 如果該節點的父節點、或兄弟節點、或兄弟節點的特定方向的子節點 中,有紅色節點,則將此紅色節點旋轉過來,通過旋轉、塗黑操作,保持自父節點以來的樹的平衡;
- 如果不滿足上述條件,則通過旋轉和變色操作,使其兄弟分支上也減少一個黑色節點,這樣自父節點以來的分支保持了平衡,滿足了條件,但對於父節點來說,其整個分支減少了一個黑色節點,需要遞歸向上處理,直至重新平衡,或者到達根節點;
掌握了整體思路以后,就可編碼實現了,編碼中用了一些小技巧,合並了一些情況,代碼比較簡單易懂,閱讀者可以根據代碼的情況自己畫圖證明:
說明:代碼為dart語言實現,dart語法基本與Java一致,不清楚的地方可以參考:
https://www.dartlang.org/guides/language/language-tour
1 // 刪除 2 bool delete(E value) { 3 var node = find(value); 4 if (node == null) return false; 5 _delete(node); 6 _nodeNumbers--; 7 return true; 8 } 9 10 // 刪除轉移 並修復 11 void _delete(RBTNode<E> d) { 12 if (d.left != null && d.right != null) { 13 var s = _successor(d); 14 d.value = s.value; 15 d = s; 16 } 17 18 var rp = d.left ?? d.right; 19 rp?.parent = d.parent; 20 if (d.parent == null) 21 _root = rp; 22 else if (d == d.parent.left) 23 d.parent.left = rp; 24 else 25 d.parent.right = rp; 26 27 if (rp != null) 28 rp.paintBlack(); 29 else if (d.isBlack && d.parent != null) 30 _fixAfterDelete(d.parent, d.parent.left == null); 31 } 32 33 RBTNode<E> _successor(RBTNode<E> d) => 34 d.right != null ? _minNode(d.right) : d.left; 35 36 RBTNode<E> _minNode(RBTNode<E> r) => r.left == null ? r : _minNode(r.left); 37 38 // fix up after delete 39 void _fixAfterDelete(RBTNode<E> p, bool isLeft) { 40 var ch = isLeft ? p.right : p.left; 41 if (isLeft) { // 如果被刪除節點是父節點p的左分支; 42 if (p.isRed) { // 如果父節點為紅,則兄弟節點ch一定為黑; 43 if (ch.left != null && ch.left.isRed) { 44 p.paintBlack(); 45 _rotateRight(ch); 46 } 47 _rotateLeft(p); 48 } else if (ch.isRed) { // 兄弟節點為紅,此時兄弟節點一定有兩個非空黑色子節點; 49 p.paintRed(); 50 ch.paintBlack(); 51 _rotateLeft(p); 52 _fixAfterDelete(p, true); // 變換為父節點為紅的情況,遞歸處理; 53 } else if (ch.left != null && ch.left.isRed) { // 父、兄均為黑,兄有紅色左孩子; 54 ch.left.paintBlack(); 55 _rotateRight(ch); 56 _rotateLeft(p); 57 } else { // 父兄均為黑,將父分支左右均減少一個黑節點,然后遞歸向上處理; 58 p.paintRed(); 59 _rotateLeft(p); 60 if (ch.parent != null) _fixAfterDelete(ch.parent, ch == ch.parent.left); 61 } 62 } else { // symmetric 63 if (p.isRed) { 64 if (ch.right != null && ch.right.isRed) { 65 p.paintBlack(); 66 _rotateLeft(ch); 67 } 68 _rotateRight(p); 69 } else if (ch.isRed) { 70 p.paintRed(); 71 ch.paintBlack(); 72 _rotateRight(p); 73 _fixAfterDelete(p, false); 74 } else if (ch.right != null && ch.right.isRed) { 75 ch.right.paintBlack(); 76 _rotateLeft(ch); 77 _rotateRight(p); 78 } else { 79 p.paintRed(); 80 _rotateRight(p); 81 if (ch.parent != null) _fixAfterDelete(ch.parent, ch == ch.parent.left); 82 } 83 } 84 }
旋轉操作的代碼:
1 void _rotateLeft(RBTNode<E> node) { 2 var r = node.right, p = node.parent; 3 r.parent = p; 4 if (p == null) 5 _root = r; 6 else if (p.left == node) 7 p.left = r; 8 else 9 p.right = r; 10 11 node.right = r.left; 12 r.left?.parent = node; 13 r.left = node; 14 node.parent = r; 15 } 16 17 void _rotateRight(RBTNode<E> node) { 18 var l = node.left, p = node.parent; 19 l.parent = p; 20 if (p == null) 21 _root = l; 22 else if (p.left == node) 23 p.left = l; 24 else 25 p.right = l; 26 27 node.left = l.right; 28 l.right?.parent = node; 29 l.right = node; 30 node.parent = l; 31 }