資料結構與演算法(十三)——紅黑樹2
三、刪除
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、程式碼
程式碼示例:完整的紅黑樹插入及刪除
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 用於讀者驗證上述程式碼的結果。上述測試案例構建的紅黑樹為: