【数据结构】红黑树

ゝ一纸荒年。 2023-01-07 03:53 205阅读 0赞

目录

  • 红黑树介绍
    • 下面的这棵是红黑树吗?
  • 红黑树 与 4阶B树(等价性)
    • 红黑树 与 2-3-4树 等价转换
  • 红黑树基础代码
    • 一些辅助函数
  • 添加(12种情况)
    • 1、parent 为 BLACK【 4 种】
    • 2、parent 为 RED
      • Ⅰ、uncle 节点是红色【上溢的情况 4 种】
      • Ⅱ、uncle 节点不是红色【旋转的情况 4 种】
    • 添加代码
  • 删除(12种情况)
    • 1、删除 RED 节点【 3 种】
    • 2、删除 BLACK 节点
      • Ⅰ、拥有 2 个 RED 子节点的 BLACK 节点
      • Ⅱ、拥有 1 个 RED 子节点的 BLACK 节点【 2 种 】
      • Ⅲ、BLACK 叶子节点
        • 兄弟节点 sibling 为 BLACK【 4 种 】
        • 兄弟节点 sibling 为 RED
    • 删除代码
  • 红黑树的完整代码
  • 红黑树的平衡
  • 平均时间复杂度
  • AVL树 vs 红黑树
  • BST vs AVL Tree vs Red Black Tree

红黑树介绍

在这里插入图片描述

红黑树也是一种自平衡的二叉搜索树

  • 以前也叫做平衡二叉B树(Symmetric Binary B-tree)
    在这里插入图片描述

红黑树必须满足以下5 条性质

  1. 节点是RED 或者BLACK
  2. 根节点是BLACK
  3. 叶子节点(外部节点,空节点)都是BLACK
  4. RED节点的子节点都是BLACK
    RED节点的parent 都是BLACK
    ② 从根节点到叶子节点的所有路径上不能有 2 个连续的RED节点
  5. 从任意节点到叶子节点的所有路径都包含相同数目的BLACK节点

下面的这棵是红黑树吗?

不是
在这里插入图片描述

红黑树 与 4阶B树(等价性)

在这里插入图片描述
在这里插入图片描述
红黑树 和 4阶B树(2-3-4树)具有等价性

  • BLACK节点与它的RED子节点融合在一起,形成1个B树节点(在B树节点中,黑色节点永远是父节点)

上面那张红黑树变成了下面这个样子:
在这里插入图片描述

  • 红黑树的 BLACK节点个数 与 4阶B树的节点总个数 相等;

下面是一个与上面的红黑树等价的 4阶B树;
在这里插入图片描述
网上有些教程用 2-3树 与 红黑树 进行类比,这是极其不严谨的,2-3树 并不能完美匹配 红黑树 的所有情况。
由于界面有限,后面展示的红黑树都会省略 黑色NULL节点

红黑树 与 2-3-4树 等价转换

在这里插入图片描述
思考:如果上图最底层的 BLACK 节点是不存在的,在 B树 中是什么样的情形?

  • 整棵 B树 只有1个节点,而且是超级节点

红黑树基础代码

在这里插入图片描述

一些辅助函数

  1. /**
  2. * 对传入的节点染色,并返回染色后的该节点
  3. */
  4. private Node<E> color(Node<E> node, boolean color) {
  5. if (node == null) return node;
  6. ((RBTNode<E>) node).color = color;
  7. return node;
  8. }
  9. /**
  10. * 染成红色
  11. */
  12. private Node<E> red(Node<E> node) {
  13. return color(node, RED);
  14. }
  15. /**
  16. * 染成黑色
  17. */
  18. private Node<E> black(Node<E> node) {
  19. return color(node, BLACK);
  20. }
  21. /**
  22. * 判断当前节点是什么颜色
  23. * @return 返回 BLACK 或 RED
  24. */
  25. private boolean colorOf(Node<E> node) {
  26. // 如果节点是null,说明是空节点,返回black,否则返回节点本身的颜色
  27. return node == null ? BLACK : ((RBTNode<E>) node).color;
  28. }
  29. /**
  30. * 判断当前节点是否是黑颜色
  31. * @return true 或 false
  32. */
  33. private boolean isBlack(Node<E> node) {
  34. return colorOf(node) == BLACK;
  35. }
  36. /**
  37. * 判断当前节点是否是红颜色
  38. * @return true 或 false
  39. */
  40. private boolean isRed(Node<E> node) {
  41. return colorOf(node) == RED;
  42. }

添加(12种情况)

已知

  • B树中,新元素必定是添加到叶子节点中
  • 4阶B树所有节点的元素个数 x 都符合 1 ≤ x ≤ 3
  • 建议新添加的节点默认为 RED,这样能够让红黑树的性质尽快满足(性质 1、2、3、5 都满足,性质 4 不一定)
    在这里插入图片描述

添加有12种情况:
新添加的节点(默认红色)

1、当 parent 是黑色,不做任何处理,添加完成后任是一棵红黑树。

2、当 parent 不是黑色(为红色):即当前节点和父节点都是红色的情况(double red)

 Ⅰ:其中4种为上溢的情况:【判定条件:uncle 节点是红色】
  ①:上溢【LL】【RR】【LR】【RL】:
   将 parent、uncle 染成黑色,然后 grand 向上合并,并且将 grand 染成红色当作新节点处理【递归】;
   grand 向上合并时,可能会继续发生上溢,若上溢到根节点,只需将根节点染成黑色。

 Ⅱ:其中4种为旋转的情况:【判定条件:uncle 节点不是红色(是黑色)】
  ①:旋转【LL】【RR】
   将 parent 染成黑色,grand 染成红色,
   然后对 grand 进行旋转,
    若 grand 是 LL 的情况:右旋转,
    若 grand 是 RR 的情况:左旋转。
  ②:旋转【LR】【RL】
   将自己染成黑色,grand 染成红色,
   然后对 grand 进行旋转,
    若 grand 是 LR 的情况:parent 左旋转、grand 右旋转,
    若 grand 是 RL 的情况:parent 右旋转、grand 左旋转。

1、parent 为 BLACK【 4 种】

有 4 种情况满足红黑树的性质 4 :parent 为 BLACK

  • 同样也满足 4阶B树 的性质
  • 因此不用做任何额外处理
    在这里插入图片描述

2、parent 为 RED

有 8 种情况不满足红黑树的性质 4 :parent 为 RED( Double Red )

Ⅰ、uncle 节点是红色【上溢的情况 4 种】

其中这 4 种属于B树节点上溢的情况【LL】【RR】【LR】【RL】
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Ⅱ、uncle 节点不是红色【旋转的情况 4 种】

其中这 4 种为旋转的情况
在这里插入图片描述
在这里插入图片描述

添加代码

  1. @Override
  2. protected void afterAdd(Node<E> node) {
  3. Node<E> parent = node.parent;
  4. // 添加的是根节点 或者 上溢到达了根节点
  5. if (parent == null) {
  6. black(node);
  7. return;
  8. }
  9. // 如果父节点是黑色,直接返回
  10. if (isBlack(parent)) return;
  11. // 叔父节点
  12. Node<E> uncle = parent.sibling();
  13. // 祖父节点
  14. Node<E> grand = parent.parent;
  15. if (isRed(uncle)) {
  16. // 叔父节点是红色【B树节点上溢】
  17. black(parent);
  18. black(uncle);
  19. // 把祖父节点当做是新添加的节点
  20. afterAdd(red(grand));
  21. } else {
  22. // 叔父节点不是红色【旋转】
  23. if (parent.isLeftChild()) {
  24. // L
  25. if (node.isLeftChild()) {
  26. // LL
  27. red(grand);
  28. black(parent);
  29. rotateRight(grand);
  30. } else {
  31. // LR
  32. red(grand);
  33. black(node);
  34. rotateLeft(parent);
  35. rotateRight(grand);
  36. }
  37. } else {
  38. // R
  39. if (node.isLeftChild()) {
  40. // RL
  41. red(grand);
  42. black(node);
  43. rotateRight(parent);
  44. rotateLeft(grand);
  45. } else {
  46. // RR
  47. red(grand);
  48. black(parent);
  49. rotateLeft(grand);
  50. }
  51. }
  52. }
  53. }

删除(12种情况)

◼ B树中,最后真正被删除的元素都在叶子节点中
在这里插入图片描述

1、删除 RED 节点【 3 种】

◼ 直接删除,不用作任何调整
在这里插入图片描述

2、删除 BLACK 节点

◼ BLACK 节点,有 2 个 RED 子节点(拥有 2 个 RED 子节点的 BLACK 节点)
✓ 不可能被直接删除,因为会找它的子节点替代删除
✓ 因此不用考虑这种情况

◼ BLACK 节点,有 1 个 RED 子节点(拥有 1 个 RED 子节点的 BLACK 节点)

◼ BLACK 叶子节点

Ⅰ、拥有 2 个 RED 子节点的 BLACK 节点

Ⅱ、拥有 1 个 RED 子节点的 BLACK 节点【 2 种 】

◼ 判定条件:用以替代该节点的子节点是 RED

◼ 将替代的子节点染成 BLACK 即可保持红黑树性质
在这里插入图片描述

Ⅲ、BLACK 叶子节点

兄弟节点 sibling 为 BLACK【 4 种 】

BLACK 叶子节点被删除后,会导致B树节点下溢(比如删除88)

◼ 判断条件: sibling 至少有 1 个 RED 子节点【3种,左、右、左右节点】(可以找兄弟节点借)

◼ 步骤:
   进行旋转操作

   旋转之后的中心节点继承 parent 的颜色(parent可能为黑色 或 红色)

   旋转之后的左右节点染为 BLACK
在这里插入图片描述
◼ 判定条件:sibling 没有 RED 子节点【1种】(无法找兄弟节点借)需要看父节点的颜色

  • 若是红色(说明肯定有个黑色节点和它在同一高度),直接将红色父节点下来合并,原来的位置不会产生下溢
  • 若是黑色(说明和它在同一高度,只有它一个节点),那么黑色父节点下来合并后,父节点的位置会产生下溢,此时将父节点当成要删除的节点处理即可(递归)

步骤:
   将 sibling 染成 RED、parent 染成 BLACK 即可修复红黑树性质

◼ 如果 parent 是 BLACK

   会导致 parent 也下溢

   这时只需要把 parent 当做被删除的节点处理即可
在这里插入图片描述

兄弟节点 sibling 为 RED

◼ 如果 sibling 是 RED【站在B树的角度,要处在同一层的兄弟节点才可以借,sibling(55)是在该节点(88)的父节点(80)里,需要将sibing节点的子节点(76)变成该节点的sibing,即要将 76 变成 80 的子节点,对80进行右旋转】

步骤:
   sibling 染成 BLACK,parent 染成 RED,进行旋转

   于是又回到 sibling 是 BLACK 的情况
在这里插入图片描述

删除代码

  1. @Override
  2. protected void afterRemove(Node<E> node) {
  3. // 如果删除的节点是红色
  4. // 或者 用以取代删除节点的子节点是红色
  5. if (isRed(node)) {
  6. black(node);
  7. return;
  8. }
  9. Node<E> parent = node.parent;
  10. // 删除的是根节点
  11. if (parent == null) return;
  12. // 删除的是黑色叶子节点【下溢】
  13. // 判断被删除的node是左还是右
  14. boolean left = (parent.left == null || node.isLeftChild());
  15. // 如果left为true,兄弟节点就是right,否则就是left
  16. Node<E> sibling = left ? parent.right : parent.left;
  17. if (left) {
  18. // 被删除的节点在左边,兄弟节点在右边
  19. if (isRed(sibling)) {
  20. // 兄弟节点是红色
  21. black(sibling);
  22. red(parent);
  23. rotateLeft(parent);
  24. // 更换兄弟
  25. sibling = parent.right;
  26. }
  27. // 兄弟节点必然是黑色
  28. if (isBlack(sibling.left) && isBlack(sibling.right)) {
  29. // 兄弟节点没有1个红色子节点,父节点要向下跟兄弟节点合并
  30. boolean parentBlack = isBlack(parent);
  31. black(parent);
  32. red(sibling);
  33. if (parentBlack) {
  34. afterRemove(parent);
  35. }
  36. } else {
  37. // 兄弟节点至少有1个红色子节点,向兄弟节点借元素
  38. // 兄弟节点的左边是黑色,兄弟要先旋转
  39. if (isBlack(sibling.right)) {
  40. rotateRight(sibling);
  41. sibling = parent.right;
  42. }
  43. color(sibling, colorOf(parent));
  44. black(sibling.right);
  45. black(parent);
  46. rotateLeft(parent);
  47. }
  48. } else {
  49. // 被删除的节点在右边,兄弟节点在左边
  50. if (isRed(sibling)) {
  51. // 兄弟节点是红色
  52. black(sibling);
  53. red(parent);
  54. rotateRight(parent);
  55. // 更换兄弟
  56. sibling = parent.left;
  57. }
  58. // 兄弟节点必然是黑色
  59. if (isBlack(sibling.left) && isBlack(sibling.right)) {
  60. // 兄弟节点没有1个红色子节点,父节点要向下跟兄弟节点合并
  61. boolean parentBlack = isBlack(parent);
  62. black(parent);
  63. red(sibling);
  64. if (parentBlack) {
  65. afterRemove(parent);
  66. }
  67. } else {
  68. // 兄弟节点至少有1个红色子节点,向兄弟节点借元素
  69. // 兄弟节点的左边是黑色,兄弟要先旋转
  70. if (isBlack(sibling.left)) {
  71. rotateLeft(sibling);
  72. sibling = parent.left;
  73. }
  74. color(sibling, colorOf(parent));
  75. black(sibling.left);
  76. black(parent);
  77. rotateRight(parent);
  78. }
  79. }
  80. }

红黑树的完整代码

由于红黑树完整代码太多,单独写了一篇文章 红黑树的完整代码

红黑树的平衡

◼ 最初遗留的困惑:为何那 5 条性质,就能保证红黑树是平衡的?

 那 5 条性质,可以保证 红黑树 等价于 4阶B树
在这里插入图片描述
◼ 相比 AVL 树,红黑树的平衡标准比较宽松:没有一条路径会 大于 其他路径的2倍

◼ 是一种弱平衡、黑高度平衡

◼ 红黑树的最大高度是 2 ∗ l o g 2 ( n + 1 ) 2 ∗ log2(n + 1) 2∗log2(n+1) ,依然是 O ( l o g n ) O(logn) O(logn) 级别

平均时间复杂度

◼ 搜索:O(logn)

◼ 添加:O(logn),O(1) 次的旋转操作

◼ 删除:O(logn),O(1) 次的旋转操作

AVL树 vs 红黑树

◼ AVL树

  • 平衡标准比较严格:每个左右子树的高度差不超过 1
  • 最大高度是 1.44 ∗ l o g 2 n + 2 − 1.328 1.44 ∗ log2 n + 2 − 1.328 1.44∗log2n+2−1.328 (100W 个节点,AVL树最大树 高28)
  • 搜索、添加、删除都是 O ( l o g n ) O(logn) O(logn) 复杂度,其中添加仅需 O ( 1 ) O(1) O(1) 次旋转调整、删除最多需要 O ( l o g n ) O(logn) O(logn) 次旋转调整

◼ 红黑树

  • 平衡标准比较宽松:没有一条路径会大于其他路径的 2倍
  • 最大高度是 2 ∗ l o g 2 ( n + 1 ) 2 ∗ log2(n + 1) 2∗log2(n+1) ( 100W 个节点,红黑树最大树 高40)
  • 搜索、添加、删除都是 O ( l o g n ) O(logn) O(logn) 复杂度,其中添加、删除都仅需 O(1) 次旋转调整

◼ 搜索的次数远远大于插入和删除,选择AVL树;
 搜索、插入、删除次数几乎差不多,选择红黑树

◼ 相对于AVL树来说,红黑树牺牲了部分平衡性以换取插入/删除操作时少量的旋转操作,整体来说性能要优于AVL树

◼ 红黑树的平均统计性能优于AVL树,实际应用中更多选择使用红黑树

BST vs AVL Tree vs Red Black Tree

插入数值:10, 35, 47, 11, 5, 57, 39, 14, 27, 26, 84, 75, 63, 41, 37, 24, 96
在这里插入图片描述

发表评论

表情:
评论列表 (有 0 条评论,205人围观)

还没有评论,来说两句吧...

相关阅读

    相关 数据结构

    一. 红黑树的概念 红黑树是一颗二叉搜索树,它的每个结点增加一个存储单位来表示结点的颜色,这个颜色是red或者black,通过对任何一条从根结点到叶子结点上的颜色来约束,

    相关 数据结构 -

    数据结构 - 红黑树 - 面试常问知识点 数据结构是面试中必定考查的知识点,面试者需要掌握几种经典的数据结构:线性表(数组、链表)、栈与队列、树(二叉树、二叉查找树、平衡

    相关 数据结构_

    红黑树 红黑树也是属于一种BBST。在之前介绍的[伸展树][Link 1]中,虽然实现简单,分摊复杂度低,但是最坏情况下的操作需要O(n)时间,无法适用于对单次效率敏感的

    相关 数据结构--

    为什么要平衡 在上一节中,我们了解了 `二叉搜索树` 具有较稳定和较高的插入搜索效率。但是在某些极端情况下, 它的效率也会退化到 `链表` 的地步。 ![2018122