資料結構與演算法(十三)——紅黑樹2

L發表於2022-03-28

三、刪除

1、介紹

  紅黑樹的刪除類似於排序二叉樹,排序二叉樹主要分為三種情況:
  (1)刪除沒有左孩子且沒有右孩子的結點。即:度為0。
  (2)刪除只有左(右)孩子的結點。即:度為1。
  (3)刪除有左孩子且有右孩子的結點。即:度為2。
  由於紅黑樹還有顏色的區分,所以在上述三種情況的基礎上加上顏色,就是六種情況。以 {15, 7, 45, 3, 10, 25, 55, 1, 5, 75} 為例:

   紅黑樹有六種情況:
  (1)刪除度為 0 的黑色結點。比如:10、25。
  (2)刪除度為 0 的紅色結點。比如:1、5、75。
  (3)刪除度為 1 的黑色結點。比如:55。
  (4)刪除度為 1 的紅色結點。這種情況不存在。
  (5)刪除度為 2 的黑色結點。比如:3、15。
  (6)刪除度為 2 的紅色結點。比如:7、45。

2、說明

  論證:度為 1 的紅色結點,在紅黑樹中,是不存在的!
  所有度為 1 的情況只有以下 4 種,這裡都畫的右孩子,左孩子也是同理。

  其中:
  "黑-黑"和"紅-黑",這兩種情況,都不符合紅黑樹的性質(4)從任意一個結點到其所有葉子結點,所經過的黑色結點數目必須相等。
  "紅-紅",不符合紅黑樹的性質(5)所有紅色結點的兩個孩子結點必須是黑色(即,紅色結點不能連續)。
  只有"黑-紅"這種情況存在。所以,度為 1 的結點,也必然是"黑-紅"這種情況。

3、分析

  情況(1)刪除度為 0 的黑色結點:比較複雜,後面專門討論。
  情況(2)刪除度為 0 的紅色結點:直接刪除即可。
  情況(3)刪除度為 1 的黑色結點:必然是"黑-紅"的結構,則,刪除當前結點(A),讓孩子結點(B)代替A,並將B改為黑色。
  情況(4)刪除度為 1 的紅色結點:這種情況不存在。
  情況(5)刪除度為 2 的黑色結點:
  比如:刪除 15,用其前驅10(後繼也可以)的值代替15,再刪除10(跳到情況1)即可。

  比如:刪除 15,用其前驅10(後繼也可以)的值代替15,再刪除10(跳到情況3)即可。

  比如:刪除 15,用其前驅12(後繼也可以)的值代替15,再刪除12(跳到情況2)即可。

  情況(6)刪除度為 2 的紅色結點:同情況(5),不再贅述。
  下面,專門討論情況(1)刪除度為 0 的黑色結點。為了方便描述,先約定一下結點名稱。

 

  由於樹的左子樹和右子樹是對稱的,所以只討論一邊的情況即可。不妨令待刪除結點 C 為左孩子,右孩子的對稱情況同理即可。

4、兄弟結點B是紅

  B是紅:則 P 一定是黑色。BL、BR一定存在且是黑色。
  調整方案:先對 P 左旋;然後B 和 P 互換顏色。(需注意旋轉後,這裡的 B 就不是 C 的兄弟結點。後面的描述不贅述)。此時跳轉到下面 B(此時的B是BL,BL才是C的兄弟結點) 是黑的情況。

5、兄弟結點B是黑

  B是黑:則孩子結點BL和BR要麼不存在,要麼存在且為紅色。不可能是黑色的結點,這會違背性質(4)從任意一個結點到其所有葉子結點,所經過的黑色結點數目必須相等。
  情況一:BR存在且為紅,B的度為1。這裡包含了度為2的情況。
  調整方案:先對 P 左旋;然後B 和 P 互換顏色,將BR塗黑;最後直接刪除C。

  情況二:BR不存在,BL存在且為紅,B的度為1。
  調整方案:先對 B 右旋;然後BL 和 B 互換顏色;跳轉到上面的情況一;

  情況三:BL、BR都不存在,B的度為0。
  調整方案:這裡,又要分兩種情況討論,P是紅色還是黑色?
  (1)P是紅色
  調整方案:P 和 B 互換顏色;直接刪除C。

  (2)P是黑色
  調整方案:將 B 塗紅;直接刪除C;將node指向 P,遞迴進行平衡調整(不再刪除結點),直到 node 指向根 root 結點。
  說明:最後一步有點不好理解。刪除C後,P的左右子樹黑色結點數相等了。但是經過P的路徑,即:G(P的父結點)的左(右)子樹黑色結點數會減 1。所以,需要遞迴調整 P。

6、程式碼

  程式碼示例:完整的紅黑樹插入及刪除

資料結構與演算法(十三)——紅黑樹2
  1 public class RBTree<T extends Comparable<T>> {
  2     // 根結點
  3     private RBNode<T> root;
  4 
  5     public RBTree() {
  6     }
  7 
  8     public RBTree(T[] arr) {
  9         if (arr == null || arr.length == 0) {
 10             return;
 11         }
 12 
 13         for (T i : arr) {
 14             this.add(i);
 15         }
 16     }
 17 
 18     // 查詢結點 t
 19     public RBNode<T> findRbNode(T t) {
 20         return this.findRbNode(t, root);
 21     }
 22 
 23     private RBNode<T> findRbNode(T t, RBNode<T> node) {
 24         if (t == null || node == null) {
 25             return null;
 26         }
 27 
 28         if (t.compareTo(node.value) == 0) {
 29             return node;
 30         }
 31         if (t.compareTo(node.value) < 0) {
 32             return this.findRbNode(t, node.left);
 33         } else {
 34             return this.findRbNode(t, node.right);
 35         }
 36     }
 37 
 38     // 查詢結點 t 的前驅
 39     private RBNode<T> precursor(T t) {
 40         final RBNode<T> node = this.findRbNode(t);
 41         if (node == null) {
 42             return null;
 43         }
 44         return this.precursor(node);
 45     }
 46 
 47     private RBNode<T> precursor(RBNode<T> node) {
 48         // 左子樹的最大值
 49         if (node.left != null) {
 50             RBNode<T> t = node.left;
 51             while (t.right != null) {
 52                 t = t.right;
 53             }
 54             return t;
 55         } else {
 56             // 這裡在刪除的情況下是不存在的.但是,就找前驅後繼來說是存在的.
 57             RBNode<T> temp = node.parent;
 58             RBNode<T> ch = node;
 59             while (temp != null && ch == temp.left) {
 60                 ch = temp;
 61                 temp = temp.parent;
 62             }
 63 
 64             return temp;
 65         }
 66     }
 67 
 68     // 查詢結點 t 的後繼
 69     private RBNode<T> successor(T t) {
 70         final RBNode<T> node = this.findRbNode(t);
 71         if (node == null) {
 72             return null;
 73         }
 74         return this.successor(node);
 75     }
 76 
 77     private RBNode<T> successor(RBNode<T> node) {
 78         // 右子樹的最小值
 79         if (node.right != null) {
 80             RBNode<T> t = node.right;
 81             while (t.left != null) {
 82                 t = t.left;
 83             }
 84             return t;
 85         } else {
 86             // 這裡在刪除的情況下是不存在的.但是,就找前驅後繼來說是存在的.
 87             RBNode<T> temp = node.parent;
 88             RBNode<T> ch = node;
 89             while (temp != null && ch == temp.right) {
 90                 ch = temp;
 91                 temp = temp.parent;
 92             }
 93 
 94             return temp;
 95         }
 96     }
 97 
 98     public void delete(T value) {
 99         final RBNode<T> node = this.findRbNode(value);
100         if (node == null) {
101             System.out.println("待刪除的結點:" + value + " 不存在~");
102             return;
103         }
104 
105         this.delNode(node);
106     }
107 
108     private void delNode(RBNode<T> node) {
109         final int degree = node.getDegree();
110         // 度為 0
111         if (degree == 0) {
112             // 1.紅色.直接刪
113             if (node.red) {
114                 this.freeDegree0(node);
115             } else {
116                 // 2.黑色
117                 if (node == root) {
118                     this.freeDegree0(node);
119                 } else {
120                     this.delBlackNode(node);
121                 }
122             }
123         } else if (degree == 1) {
124             // 度為 1.一定是 "黑-紅"
125             if (node.left != null) {
126                 node.value = node.left.value;
127                 node.left = null;
128             } else {
129                 node.value = node.right.value;
130                 node.right = null;
131             }
132         } else {
133             // 度為 2
134             final RBNode<T> precursor = this.precursor(node);
135             node.value = precursor.value;
136             this.delNode(precursor);
137         }
138     }
139 
140     /* 刪除度為 1 的黑色結點 */
141     private void delBlackNode(RBNode<T> node) {
142         RBNode<T> temp = node;
143 
144         // 遞迴調整
145         while (temp != root) {
146             final RBNode<T> p = temp.parent;
147             final RBNode<T> brother = temp.getBrother();
148 
149             // 兄弟 B是紅
150             if (brother.red) {
151                 this.adjustCase1(temp); // 經過adjustCase1後,兄弟是黑色
152             } else {
153                 // 兄弟 B是黑 .有孩子
154                 if (brother.left != null || brother.right != null) {
155                     if (temp == p.left) {
156                         if (brother.right != null) {
157                             this.adjustCase2(temp);
158                         } else {
159                             this.adjustCase3(temp);
160                         }
161                     } else {
162                         if (brother.left != null) {
163                             this.adjustCase2(temp);
164                         } else {
165                             this.adjustCase3(temp);
166                         }
167                     }
168 
169                     break;
170                 } else {
171                     // C-黑.兄弟 B是黑. 且沒有孩子
172                     // p-紅
173                     if (p.red) {
174                         brother.red = true;
175                         p.red = false;
176                         this.freeDegree0(temp);
177                         break;
178                     } else {
179                         // p-黑
180                         brother.red = true;
181                         this.freeDegree0(temp);
182                         temp = p;
183                     }
184                 }
185             }
186         }
187     }
188 
189     // C-黑. B-紅
190     private void adjustCase1(RBNode<T> node) {
191         final RBNode<T> p = node.parent;
192         // 左孩子.(左右對稱的)
193         if (node == p.left) {
194             this.leftRotate(p);
195         } else {
196             this.rightRotate(p);
197         }
198 
199         node.parent.red = true;
200         node.parent.parent.red = false;
201     }
202 
203     // C-黑. B-黑. BR-紅 (遠侄子)
204     private void adjustCase2(RBNode<T> node) {
205         final RBNode<T> p = node.parent;
206         if (node == p.left) {
207             this.leftRotate(p);
208 
209             // B、P顏色互換
210             node.parent.parent.red = node.parent.red;
211             node.parent.red = false;
212             // 塗黑遠侄子
213             node.parent.parent.right.red = false;
214         } else {
215             this.rightRotate(p);
216 
217             // B、P顏色互換
218             node.parent.parent.red = node.parent.red;
219             node.parent.red = false;
220             // 塗黑遠侄子
221             node.parent.parent.left.red = false;
222         }
223         this.freeDegree0(node);
224     }
225 
226     // C-黑. B-黑. BR-不存在. BL-紅 (近侄子)
227     private void adjustCase3(RBNode<T> node) {
228         final RBNode<T> p = node.parent;
229         final RBNode<T> brother = node.getBrother();
230         // C 是左孩子.BL-紅 (近侄子)
231         if (brother.left != null) {
232             rightRotate(brother);
233         } else {
234             // C 是右孩子.BR-紅 (近侄子)
235             leftRotate(brother);
236         }
237 
238         // BL 和 B 互換顏色
239         brother.red = true;
240         brother.parent.red = false;
241 
242         // 跳轉到adjustCase2
243         this.adjustCase2(p);
244     }
245 
246     // 直接刪除度為 0 的結點 node
247     private void freeDegree0(RBNode<T> node) {
248         final RBNode<T> p = node.parent;
249         // 待刪除結點 node 就是root
250         if (p == null) {
251             root = null;
252             return;
253         }
254 
255         if (node == p.left) {
256             p.left = null;
257         } else {
258             p.right = null;
259         }
260     }
261 
262     // 新增結點
263     public void add(T value) {
264         RBNode<T> newNode = new RBNode<>(value);
265         if (root == null) {
266             root = newNode;
267             newNode.red = false;
268             return;
269         }
270 
271         // 1.新增
272         this.add(root, newNode);
273 
274         // 2.調整
275         this.fixUp(newNode);
276     }
277 
278     private void fixUp(RBNode<T> newNode) {
279         if (newNode == root) {
280             newNode.red = false;
281             return;
282         }
283 
284         // newNode 的父結點為黑色
285         if (!newNode.parent.red) {
286             return;
287         }
288 
289         /* 1.newNode 的叔叔結點存在且為紅色。*/
290         final RBNode<T> uncle = newNode.getUncle();
291         if (uncle != null && uncle.red) {
292             newNode.parent.red = false;
293             uncle.red = false;
294 
295             newNode.parent.parent.red = true;
296             this.fixUp(newNode.parent.parent);
297         } else {
298             /* 2.newNode 的叔叔結點不存在,或者為黑色。*/
299             final RBNode<T> grandFather = newNode.getGrandFather();
300             // 2.1左左
301             if (newNode == grandFather.left.left) {
302                 this.rightRotate(grandFather);
303                 newNode.parent.red = false;
304                 grandFather.red = true;
305             }
306             // 2.2左右
307             else if (newNode == grandFather.left.right) {
308                 this.leftRotate(newNode.parent);
309                 this.rightRotate(grandFather);
310                 newNode.red = false;
311                 grandFather.red = true;
312             }
313             // 2.3右右
314             else if (newNode == grandFather.right.right) {
315                 this.leftRotate(grandFather);
316                 newNode.parent.red = false;
317                 grandFather.red = true;
318             }
319             // 2.4右左
320             else if (newNode == grandFather.right.left) {
321                 this.rightRotate(newNode.parent);
322                 this.leftRotate(grandFather);
323                 newNode.red = false;
324                 grandFather.red = true;
325             }
326         }
327     }
328 
329     // 按 排序二叉樹 的規則插入結點
330     private void add(RBNode<T> root, RBNode<T> newNode) {
331         if (newNode.value.compareTo(root.value) <= 0) {
332             if (root.left == null) {
333                 root.left = newNode;
334                 newNode.parent = root;
335             } else {
336                 this.add(root.left, newNode);
337             }
338         } else {
339             if (root.right == null) {
340                 root.right = newNode;
341                 newNode.parent = root;
342             } else {
343                 this.add(root.right, newNode);
344             }
345         }
346     }
347 
348     // 左旋
349     private void leftRotate(RBNode<T> node) {
350         if (node == null) {
351             return;
352         }
353         final RBNode<T> p = node.parent;
354 
355         // 左旋. 應該認為 temp 不為null
356         final RBNode<T> temp = node.right;
357         node.right = temp.left;
358         if (temp.left != null) {
359             // 該結點可能不存在
360             temp.left.parent = node;
361         }
362 
363         temp.left = node;
364         node.parent = temp;
365 
366         // 旋轉完成.修正根結點與父結點
367         // 1.node為根結點
368         if (node == root) {
369             root = temp;
370             temp.parent = null;
371             return;
372         }
373 
374         // 2.node不為根結點
375         // node 為父結點的右孩子
376         if (node == p.right) {
377             p.right = temp;
378         } else {
379             p.left = temp;
380         }
381         temp.parent = p;
382     }
383 
384     // 右旋
385     private void rightRotate(RBNode<T> node) {
386         if (node == null) {
387             return;
388         }
389 
390         final RBNode<T> p = node.parent;
391 
392         // 右旋. 應該認為 temp 不為null
393         final RBNode<T> temp = node.left;
394         node.left = temp.right;
395         if (temp.right != null) {
396             // 該結點可能不存在
397             temp.right.parent = node;
398         }
399 
400         temp.right = node;
401         node.parent = temp;
402 
403         // 旋轉完成.修正根結點與父結點
404         // 1.node為根結點
405         if (node == root) {
406             root = temp;
407             temp.parent = null;
408             return;
409         }
410 
411         // 2.node不為根結點
412         // node 為父結點的右孩子
413         if (node == p.right) {
414             p.right = temp;
415         } else {
416             p.left = temp;
417         }
418         temp.parent = p;
419     }
420 
421     // 中序遍歷
422     public void infixOrder() {
423         this.infixOrder(root);
424     }
425 
426     private void infixOrder(RBNode<T> root) {
427         if (root != null) {
428             this.infixOrder(root.left);
429             System.out.print("-->" + root.value + ":" + (root.red ? "紅" : "黑"));
430             this.infixOrder(root.right);
431         }
432     }
433 
434     /**
435      * 紅黑樹 樹結點結構
436      */
437     protected static class RBNode<T extends Comparable<T>> {
438         private T value;
439         // 預設為 紅色 結點
440         private boolean red = true;
441 
442         private RBNode<T> left;
443         private RBNode<T> right;
444         private RBNode<T> parent;
445 
446         public RBNode() {
447         }
448 
449         public RBNode(T value) {
450             this.value = value;
451         }
452 
453         // 返回結點的度
454         public int getDegree() {
455             if (this.left == null && this.right == null) {
456                 return 0;
457             }
458 
459             if ((this.left != null && this.right == null) || (this.left == null && this.right != null)) {
460                 return 1;
461             }
462 
463             return 2;
464         }
465 
466         public RBNode<T> getUncle() {
467             final RBNode<T> grandFather = this.parent.parent;
468             final RBNode<T> parent = this.parent;
469 
470             if (parent == grandFather.left) {
471                 return grandFather.right;
472             }
473 
474             if (parent == grandFather.right) {
475                 return grandFather.left;
476             }
477 
478             return null;
479         }
480 
481         public RBNode<T> getGrandFather() {
482             return this.parent.parent;
483         }
484 
485         public RBNode<T> getBrother() {
486             final RBNode<T> p = this.parent;
487 
488             return this == p.left ? p.right : p.left;
489         }
490 
491         @Override
492         public String toString() {
493             return "RBNode{" +
494                     "value=" + value +
495                     ", red=" + red +
496                     '}';
497         }
498     }
499 }
完整的紅黑樹插入及刪除

  程式碼示例:測試

 1 public class Main {
 2     public static void main(String[] args) {
 3         // Integer[] integers = {15, 7, 45, 3, 10, 25, 55, 1, 5, 75};
 4         Integer[] integers = {500, 100, 750, 25, 300, 550, 800, 15, 50, 520, 600, 510};
 5         RBTree<Integer> tree = new RBTree<>(integers);
 6         tree.infixOrder();
 7 
 8         tree.delete(300);
 9         System.out.println("");
10         tree.infixOrder();
11     }
12 }

  最後,推薦一個線上構建紅黑樹的地址:https://www.cs.usfca.edu/~galles/visualization/RedBlack.html  用於讀者驗證上述程式碼的結果。上述測試案例構建的紅黑樹為:

相關文章