数据结构与算法(十三)——红黑树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 }
最后,推荐一个在线构建红黑树的地址://www.cs.usfca.edu/~galles/visualization/RedBlack.html 用于读者验证上述代码的结果。上述测试案例构建的红黑树为: