红黑树
概述
历史
红黑树是一种自平衡二叉查找树,最早由一位名叫Rudolf Bayer的德国计算机科学家于1972年发明。然而,最初的树形结构不是现在的红黑树,而是一种称为B树的结构,它是一种多叉树,可用于在磁盘上存储大量数据。
在1980年代早期,计算机科学家Leonard Adleman和Daniel Sleator推广了红黑树,并证明了它的自平衡性和高效性。从那时起,红黑树成为了最流行的自平衡二叉查找树之一,并被广泛应用于许多领域,如编译器、操作系统、数据库等。
红黑树的名字来源于红色节点和黑色节点的交替出现,它们的颜色是用来维护树的平衡性的关键。它们的颜色具有特殊的意义,黑色节点代表普通节点,而红色节点代表一个新添加的节点,它们必须满足一些特定的规则才能维持树的平衡性。
红黑树也是一种自平衡的二叉搜索树,较之 AVL,插入和删除时旋转次数更少
红黑树特性
- 所有节点都有两种颜色:红:red_circle:、黑:black_circle:
- 所有 null 视为黑色:black_circle:
- 红色:red_circle:节点不能相邻
- 根节点是黑色:black_circle:
- 从根到任意一个叶子节点,路径中的黑色:black_circle:节点数一样
实现
插入情况
插入节点均视为红色:red_circle:
case 1:插入节点为根节点,将根节点变黑:black_circle:
case 2:插入节点的父亲若为黑色:black_circle:,树的红黑性质不变,无需调整
插入节点的父亲为红色:red_circle:,触发红红相邻
case 3:叔叔为红色:red_circle:
-
父亲变为黑色:black_circle:,为了保证黑色平衡,连带的叔叔也变为黑色:black_circle:
-
祖父如果是黑色不变,会造成这颗子树黑色过多,因此祖父节点变为红色:red_circle:
-
祖父如果变成红色,可能会接着触发红红相邻,因此对将祖父进行递归调整
case 4:叔叔为黑色:black_circle:
- 父亲为左孩子,插入节点也是左孩子,此时即 LL 不平衡
- 让父亲变黑:black_circle:,为了保证这颗子树黑色不变,将祖父变成红:red_circle:,但叔叔子树少了一个黑色
- 祖父右旋,补齐一个黑色给叔叔,父亲旋转上去取代祖父,由于它是黑色,不会再次触发红红相邻
- 父亲为左孩子,插入节点是右孩子,此时即 LR 不平衡
- 父亲为右孩子,插入节点也是右孩子,此时即 RR 不平衡
- 让父亲变黑:black_circle:,为了保证这颗子树黑色不变,将祖父变成红:red_circle:,但叔叔子树少了一个黑色
- 祖父左旋,补齐一个黑色给叔叔,父亲旋转上去取代祖父,由于它是黑色,不会再次触发红红相邻
- 父亲为右孩子,插入节点是左孩子,此时即 RL 不平衡
删除情况
case0:如果删除节点有两个孩子
- 交换删除节点和后继节点的 key,value,递归删除后继节点,直到该节点没有孩子或只剩一个孩子
如果删除节点没有孩子或只剩一个孩子
case 1:删的是根节点
- 删完了,直接将 root = null
- 用剩余节点替换了根节点的 key,value,根节点孩子 = null,颜色保持黑色:black_circle:不变
删黑色会失衡,删红色不会失衡,但删黑色有一种简单情况
case 2:删的是黑:black_circle:,剩下的是红:red_circle:,剩下这个红节点变黑:black_circle:
删除节点和剩下节点都是黑:black_circle:,触发双黑,双黑意思是,少了一个黑
case 3:被调整节点的兄弟为红:red_circle:,此时两个侄子定为黑 :black_circle:
- 删除节点是左孩子,父亲左旋
- 删除节点是右孩子,父亲右旋
- 父亲和兄弟要变色,保证旋转后颜色平衡
- 旋转的目的是让黑侄子变为删除节点的黑兄弟,对删除节点再次递归,进入 case 4 或 case 5
case 4:被调整节点的兄弟为黑:black_circle:,两个侄子都为黑 :black_circle:
- 将兄弟变红:red_circle:,目的是将删除节点和兄弟那边的黑色高度同时减少 1
- 如果父亲是红:red_circle:,则需将父亲变为黑,避免红红,此时路径黑节点数目不变
- 如果父亲是黑:black_circle:,说明这条路径还是少黑,再次让父节点触发双黑
case 5:被调整节点的兄弟为黑:black_circle:,至少一个红:red_circle:侄子
- 如果兄弟是左孩子,左侄子是红:red_circle:,LL 不平衡
- 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑:black_circle:,平衡起见,左侄子也是黑:black_circle:
- 原来兄弟要成为父亲,需要保留父亲颜色
- 如果兄弟是左孩子,右侄子是红:red_circle:,LR 不平衡
- 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑:black_circle:
- 右侄子会取代原来父亲,因此它保留父亲颜色
- 兄弟已经是黑了:black_circle:,无需改变
- 如果兄弟是右孩子,右侄子是红:red_circle:,RR 不平衡
- 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑:black_circle:,平衡起见,右侄子也是黑:black_circle:
- 原来兄弟要成为父亲,需要保留父亲颜色
- 如果兄弟是右孩子,左侄子是红:red_circle:,RL 不平衡
- 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑:black_circle:
- 左侄子会取代原来父亲,因此它保留父亲颜色
- 兄弟已经是黑了:black_circle:,无需改变
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390
| package com.itheima.datastructure.redblacktree;
import static com.itheima.datastructure.redblacktree.RedBlackTree.Color.BLACK; import static com.itheima.datastructure.redblacktree.RedBlackTree.Color.RED;
public class RedBlackTree {
enum Color { RED, BLACK; }
Node root;
static class Node { int key; Object value; Node left; Node right; Node parent; Color color = RED;
public Node(int key, Object value) { this.key = key; this.value = value; }
public Node(int key) { this.key = key; }
public Node(int key, Color color) { this.key = key; this.color = color; }
public Node(int key, Color color, Node left, Node right) { this.key = key; this.color = color; this.left = left; this.right = right; if (left != null) { left.parent = this; } if (right != null) { right.parent = this; } }
boolean isLeftChild() { return parent != null && parent.left == this; }
Node uncle() { if (parent == null || parent.parent == null) { return null; } if (parent.isLeftChild()) { return parent.parent.right; } else { return parent.parent.left; } }
Node sibling() { if (parent == null) { return null; } if (this.isLeftChild()) { return parent.right; } else { return parent.left; } } }
boolean isRed(Node node) { return node != null && node.color == RED; }
boolean isBlack(Node node) {
return node == null || node.color == BLACK; }
private void rightRotate(Node pink) { Node parent = pink.parent; Node yellow = pink.left; Node green = yellow.right; if (green != null) { green.parent = pink; } yellow.right = pink; yellow.parent = parent; pink.left = green; pink.parent = yellow; if (parent == null) { root = yellow; } else if (parent.left == pink) { parent.left = yellow; } else { parent.right = yellow; } }
private void leftRotate(Node pink) { Node parent = pink.parent; Node yellow = pink.right; Node green = yellow.left; if (green != null) { green.parent = pink; } yellow.left = pink; yellow.parent = parent; pink.right = green; pink.parent = yellow; if (parent == null) { root = yellow; } else if (parent.left == pink) { parent.left = yellow; } else { parent.right = yellow; } }
public void put(int key, Object value) { Node p = root; Node parent = null; while (p != null) { parent = p; if (key < p.key) { p = p.left; } else if (p.key < key) { p = p.right; } else { p.value = value; return; } } Node inserted = new Node(key, value); if (parent == null) { root = inserted; } else if (key < parent.key) { parent.left = inserted; inserted.parent = parent; } else { parent.right = inserted; inserted.parent = parent; } fixRedRed(inserted); }
void fixRedRed(Node x) { if (x == root) { x.color = BLACK; return; } if (isBlack(x.parent)) { return; }
Node parent = x.parent; Node uncle = x.uncle(); Node grandparent = parent.parent; if (isRed(uncle)) { parent.color = BLACK; uncle.color = BLACK; grandparent.color = RED; fixRedRed(grandparent); return; }
if (parent.isLeftChild() && x.isLeftChild()) { parent.color = BLACK; grandparent.color = RED; rightRotate(grandparent); } else if (parent.isLeftChild()) { leftRotate(parent); x.color = BLACK; grandparent.color = RED; rightRotate(grandparent); } else if (!x.isLeftChild()) { parent.color = BLACK; grandparent.color = RED; leftRotate(grandparent); } else { rightRotate(parent); x.color = BLACK; grandparent.color = RED; leftRotate(grandparent); } }
public void remove(int key) { Node deleted = find(key); if (deleted == null) { return; } doRemove(deleted); }
public boolean contains(int key) { return find(key) != null; }
private Node find(int key) { Node p = root; while (p != null) { if (key < p.key) { p = p.left; } else if (p.key < key) { p = p.right; } else { return p; } } return null; }
private Node findReplaced(Node deleted) { if (deleted.left == null && deleted.right == null) { return null; } if (deleted.left == null) { return deleted.right; } if (deleted.right == null) { return deleted.left; } Node s = deleted.right; while (s.left != null) { s = s.left; } return s; }
private void fixDoubleBlack(Node x) { if (x == root) { return; } Node parent = x.parent; Node sibling = x.sibling(); if (isRed(sibling)) { if (x.isLeftChild()) { leftRotate(parent); } else { rightRotate(parent); } parent.color = RED; sibling.color = BLACK; fixDoubleBlack(x); return; } if (sibling != null) { if (isBlack(sibling.left) && isBlack(sibling.right)) { sibling.color = RED; if (isRed(parent)) { parent.color = BLACK; } else { fixDoubleBlack(parent); } } else { if (sibling.isLeftChild() && isRed(sibling.left)) { rightRotate(parent); sibling.left.color = BLACK; sibling.color = parent.color; } else if (sibling.isLeftChild() && isRed(sibling.right)) { sibling.right.color = parent.color; leftRotate(sibling); rightRotate(parent); } else if (!sibling.isLeftChild() && isRed(sibling.left)) { sibling.left.color = parent.color; rightRotate(sibling); leftRotate(parent); } else { leftRotate(parent); sibling.right.color = BLACK; sibling.color = parent.color; } parent.color = BLACK; } } else { fixDoubleBlack(parent); } }
private void doRemove(Node deleted) { Node replaced = findReplaced(deleted); Node parent = deleted.parent; if (replaced == null) { if (deleted == root) { root = null; } else { if (isBlack(deleted)) { fixDoubleBlack(deleted); } else { } if (deleted.isLeftChild()) { parent.left = null; } else { parent.right = null; } deleted.parent = null; } return; } if (deleted.left == null || deleted.right == null) { if (deleted == root) { root.key = replaced.key; root.value = replaced.value; root.left = root.right = null; } else { if (deleted.isLeftChild()) { parent.left = replaced; } else { parent.right = replaced; } replaced.parent = parent; deleted.left = deleted.right = deleted.parent = null; if (isBlack(deleted) && isBlack(replaced)) { fixDoubleBlack(replaced); } else { replaced.color = BLACK; } } return; } int t = deleted.key; deleted.key = replaced.key; replaced.key = t;
Object v = deleted.value; deleted.value = replaced.value; replaced.value = v; doRemove(replaced); } }
|
小结
维度 |
普通二叉搜索树 |
AVL树 |
红黑树 |
查询 |
平均O(logn),最坏O(n) |
O(logn) |
O(logn) |
插入 |
平均O(logn),最坏O(n) |
O(logn) |
O(logn) |
删除 |
平均O(logn),最坏O(n) |
O(logn) |
O(logn) |
平衡性 |
不平衡 |
严格平衡 |
近似平衡 |
结构 |
二叉树 |
自平衡的二叉树 |
具有红黑性质的自平衡二叉树 |
查找效率 |
低 |
高 |
高 |
插入删除效率 |
低 |
中等 |
高 |
普通二叉搜索树插入、删除、查询的时间复杂度与树的高度相关,因此在最坏情况下,时间复杂度为O(n),而且容易退化成链表,查找效率低。
AVL树是一种高度平衡的二叉搜索树,其左右子树的高度差不超过1。因此,它能够在logn的平均时间内完成插入、删除、查询操作,但是在维护平衡的过程中,需要频繁地进行旋转操作,导致插入删除效率较低。
红黑树是一种近似平衡的二叉搜索树,它在保持高度平衡的同时,又能够保持较高的插入删除效率。红黑树通过节点着色和旋转操作来维护平衡。红黑树在维护平衡的过程中,能够进行较少的节点旋转操作,因此插入删除效率较高,并且查询效率也较高。
综上所述,红黑树具有较高的综合性能,是一种广泛应用的数据结构。
AVL树
B树