紅黑樹的刪除詳解與思路分析——不一樣於教科書上的算法(dart語言實現)

對於紅黑樹的刪除,看了數據結構的書,也看了不少網上的講解和實現,但都不滿意。不少講解都是囫圇吞棗,知其然,不知其因此然,講的晦澀難懂。node

紅黑樹是平衡二叉樹的一種,其刪除算法是比較複雜的,由於刪除後還要保持紅黑樹的特性。紅黑樹的特性以下:算法

    1. 節點是紅色或黑色。
    2. 根是黑色。
    3. 全部葉子都是黑色(葉子是NIL節點)。
    4. 每一個紅色節點必須有兩個黑色的子節點。(從每一個葉子到根的全部路徑上不能有兩個連續的紅色節點。)
    5. 從任一節點到其每一個葉子的全部簡單路徑都包含相同數目的黑色節點(簡稱黑高)。

所以,從紅黑樹最基礎的特性出發,拋開教科書和網上的算法,畫了無數張圖,分析了多種可能的狀況之後,通過概括提煉,實現了不一樣於教科書上的刪除算法。網絡

通過屢次畫圖證實之後,筆者發現,紅黑樹的刪除算法不是惟一的,無論如何調整,只要保證刪除後仍是一顆紅黑樹便可。數據結構

所以,筆者實現的 刪除思路和算法以下:ide

  1. 刪除轉移:(這部分是大路貨,不是本身實現的)ui

    • 若是被刪除節點有兩個非空子節點,則用後繼節點的值代替該節點的值,這樣演變成了刪除後繼節點;不然轉下一條;
    • 若是被刪除節點一個或兩個孩子都爲空:如有非空孩子,則用非空孩子節點替代之;若無,直接刪除;
    • 刪除後繼節點:後繼節點的左孩子節點必定爲空,右孩子可能爲空;處理如上一條;

  刪除轉移的目的是爲了簡化刪除操做,更是爲了簡化修復操做。由於刪除轉移後,最終待刪除的節點最多隻會有一個非空孩子。編碼

 

  2. 刪除後修復:spa

  2.1 簡單的狀況:code

    • 若被刪除節點爲紅色節點,不需修復;此時該節點必定爲紅色的葉子節點(可根據紅黑樹的特性證實);
    • 若被刪除的節點爲黑色節點,且有一個非空子節點,則將其非空子節點顏色塗黑便可;

    對於以上兩種簡單的狀況,作個說明:根據紅黑樹特性,非空子節點必定爲紅色節點,不然將違反特性;根據紅黑樹特性,在刪除前,一顆紅黑樹不可能出現如下幾種狀況:blog

(圖片來自網絡,感謝原做者。)

   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   }
相關文章
相關標籤/搜索