• RBTree(红黑树)模拟实现(插入)


    目录

    红黑树的性质

    红黑树的模拟插入

    叔叔存在且为红色

    叔叔不存在

    旋转情况​​​​​​​

    叔叔存在且为黑色

    总结

    插入实现

    节点

    插入逻辑

    左单旋

    右单旋


    红黑树是一颗平衡搜索二叉树,但是红黑树并不像 AVL 树一样是高度平衡二叉树,任意一颗红黑树,它的子树不会超出它任意一个子树高度的二倍。

    红黑树的性质

    • 每个节点不是红色就是黑色
    • 根节点是黑色的
    • 每个叶子节点(nil 节点/空节点)都是黑色的
    • 如果一个叶子节点是红色的,那么它的孩子节点都必须是黑色的
    • 任意一条路径上包含的黑色节点的数量都是相等的

    其中,红黑树的平衡,就是由上面五条决定的。但是这里看到上面的五条并没有提到高度等字眼,红黑树也并不靠高度来维持平衡。

    所以通过上面的条件,红黑树的高度虽然比 AVL 树的高度要高,但是红黑树的旋转次数是要比 AVL 树的少的,对于计算机而言,虽然高度可能比 AVL 树高,但是就搜索树的搜索时间复杂度为 O(log n) 来说,即使是红黑树的高度要相差二倍,那么时间复杂度最差也就是 O(log 2n) ,而对于计算机来说,这点时间并不算什么。

    红黑树的模拟插入

    红黑树的插入也是有几种情况,这几种情况分别需要不同的对待,所以下面我们看一下。

    但是在插入之前,我们先想个问题,我们在插入的时候是插入红色节点呢?还是黑色节点?

    • 如果我们插入黑色节点,那么单条路径上的黑色节点的个数就变化了,所以我们还需要去修改其他路径的黑色节点,所以插入黑色节点的话,需要修改的节点数比较多
    • 插入红色节点,插入红色节点的话,我们的红黑树可能就不满足了,因为如果插入节点的父亲节点是红色的,那么就不满足红色节点的孩子节点必须是黑色的,所以这时候我们就需要对它的父亲节点进行修改颜色,或者是它的叔叔,并不会像插入黑色节点那样,需要对其他的路径都做修改
    • 所以,如果我们插入黑色节点的话,需要调整的工作量就比较大,但是如果我们插入红色节点的话,是有可能是不需要调整的,因为可能插入的节点的父亲节点就是黑色的,就算插入的父亲节点是红色的,那么也只需要修改它的父亲节点,或者是叔叔节点

    叔叔存在且为红色

    这里有一颗红黑树:

     假设我们现在要插入的节点是 21:

    这时候,我们看到 cur 位置已经插入了节点 21,但是插入之后,该红黑树违反了,“红色节点的孩子节点必须是黑色的” 这一条规定,所以为例维持红黑树,我们需要对其进行变色,或者是旋转等操作来使其依旧是红黑树。

    既然上面违反了 “红色几点的孩子节点必须是黑色的”,那么我们在看一下,cur 有没有叔叔节点,如果有的话,那么我们在看一下叔叔节点的颜色是不是红色的,如果都满足的话,那么下面我们就把父亲节点和叔叔节点都变为黑色的,然后把祖父节点变为红色,这样的话,这两条路径的黑色节点数量就不变了:

    但是这里变色结束后,我们看到祖父节点是红色的,祖父节点的父亲节点也是红色的,所以我们可以继续刚才的步骤,知道祖父节点的颜色变为黑色,或者是祖父节点没有父亲节点,或者是其他情况的时候就结束,所以这时候,我们将祖父节点赋值给 cur 节点,然后继续啊上面的循环:

    这时候,我们的祖父节点没有父亲节点,也就表示祖父节点就是根了,那么也就可以跳出循环,不过跳出循环后,我们的根节点不满足“根节点是黑色的”,这一条规定,所以出了循环之后,我们可以把根节点置为黑色:

    在这样变色之后,该红黑树还是红黑树。

    上面就是叔叔存在且为红色的情况。

    叔叔不存在

    如果是这种情况的话,那么就是叔叔不存在的情况,我们已经插入了 cur 节点,而这时候就是叔叔不存在的情况,那么这时候就不能靠改变父亲的颜色来维持红黑树了,因为这里只把父亲的颜色改变后和把祖父的颜色改变后,那么最右边的路径上黑色节点的数量就变少了,与其他路径的黑色节点的数量不同,所以不能光改变父亲和祖父的颜色,这时候就需要旋转加变色,那么怎么样旋转?这里我们可以对 grandparent 节点进行右单旋,然后将 parent 节点变为黑色,祖父节点变为红色:

     下一步就是将祖父节点变为红色,父亲节点变为黑色:

    经过这样的旋转和变色之后,该树还是红黑树,不过刚才使用的是右单旋,那么怎么样判断是左单旋,还是右单旋,亦或者是右左双旋,或者是左右双旋?

    旋转情况

    在红黑树的旋转的判断条件并不是高度,而是看 cur parent 以及 grandparent 三个节点的位置。

    1. 如果 parent 是 grandparent 的 left ,并且 cur 也是 parent 的 left ,那么就使用的是右单旋

    2. 如果 parent 是 grandparent 的 right,并且 cur 也是 parent 的 right,那么就使用的是左单旋

    3. 如果 parent 是 grandparent 的 right,并且 cur 也是 parent 的 left ,那么就使用的是右左双旋

    4. 如果 parent 是 grandparent 的 left ,并且 cur 也是 parent 的 right,那么就使用的是左右双旋

    所以下面我们看一下叔叔不存在,双旋的情况:

    这时候,我们插入的节点是 24 这个节点,然后我们发现叔叔不存在,所以我们需要使用旋转加变色的方案,我们发现 parent 是 grandparent 的左,而 cur 是 parent 的右,所以我们需要使用左右双旋:

    先对 parent 进行左单旋

     在对 grandparent 进行右单旋

    旋转结束后,我们发现颜色不正确,所以我们在这种情况下,需要将grandparent 变为 红色,然后将 cur 变为 黑色。

    叔叔存在且为黑色

    上面就是叔叔存在且为黑色,我们到 cur 位置插入,但是这里我们先进行叔叔存在且为红色,所以我们向上跟新:

    到这时候,就变成叔叔存在且为黑色了,所以这时候我们也不能单靠变色来解决问题,我们需要旋转加变色。

    这时候的旋转也是按照上面的规则来的,我们看到parent 是 grandparent 的 右边,cur 是 parent 的右边所以这时候我们使用的是左单旋:

    旋转结束后就是这个样子,但是颜色并不符合红黑树,所以我们还需要变色来处理,这种情况下,我们只需要把 grandparent 变为红色,父亲变为黑色:

    其实叔叔不存在和叔叔存在且为黑色的情况是相同的,如果是单旋的话,那么就是旋转结束后,将付清的颜色变为黑色,然后将祖父的颜色变为红色,如果是双旋的话,那么就是将 cur 的颜色变为黑色,祖父的颜色变为红色。

    下面看一下双旋的情况

    这时候 cur 还是插入的节点,这时候我们跟新一次,然后就可以达到叔叔存在且为黑色,并且还是双旋的情况:

     此时就是叔叔存在且为黑色,并且我们发现 parent 是grandparent 的右边,cur 是 parent 的左边,所以这里我们使用的是右左双旋:

    对 parent 进行右单旋

    在对 grandparent 使用左单旋转

     这时候,就是将 grandparent 变为红色,然后将 cur 变为 黑色,所以我们在模拟插入后,发现叔叔不存在和存在且为黑的情况是一样的,所以我们后面就可以将叔叔存在且为红色分为一类,和叔叔不存在,或者存在且为黑色,分为一类,将这两类分开处理。

    总结

    旋转规则上面以及说过了,下面说一下变色:

    1. 单旋情况下,将 grandparent 变为红色, parent 变为黑色

    2. 双旋情况下,将 grandparent 变为红色, cur 变为黑色

    插入实现

    节点

    • 红黑树同样是kv结构,所以需要一个存储kv的变量
    • 既然是红黑树,那么除了三叉链,当然还需要一个颜色来控制
    1. enum Colour
    2. {
    3. RED,
    4. BLACK
    5. };
    6. template<class K, class V>
    7. struct RBTreeNode
    8. {
    9. pair _kv;
    10. RBTreeNode* _left;
    11. RBTreeNode* _right;
    12. RBTreeNode* _parent;
    13. Colour _col;
    14. RBTreeNode(const pair& kv)
    15. :_kv(kv)
    16. ,_left(nullptr)
    17. ,_right(nullptr)
    18. ,_parent(nullptr)
    19. ,_col(RED)
    20. {}
    21. };

    插入逻辑

    1. bool insert(const pair& kv)
    2. {
    3. if (_root == nullptr)
    4. {
    5. // 根节点为空,插入到根节点
    6. _root = new Node(kv);
    7. _root->_col = BLACK;
    8. return true;
    9. }
    10. Node* cur = _root, *parent = nullptr;
    11. while (cur)
    12. {
    13. if (cur->_kv.first < kv.first)
    14. {
    15. parent = cur;
    16. cur = cur->_right;
    17. }
    18. else if (cur->_kv.first > kv.first)
    19. {
    20. parent = cur;
    21. cur = cur->_left;
    22. }
    23. else
    24. {
    25. // 里面右重复值,插入失败
    26. return false;
    27. }
    28. }
    29. // 找到插入位置了
    30. cur = new Node(kv);
    31. cur->_parent = parent;
    32. if (parent->_kv.first < kv.first)
    33. parent->_right = cur;
    34. else
    35. parent->_left = cur;
    36. // 维护红黑树
    37. //当父亲不为空,并且父亲的颜色是红色就继续调整
    38. while (parent && parent->_col == RED)
    39. {
    40. Node* grandfather = parent->_parent;
    41. if (parent == grandfather->_left)
    42. {
    43. // parent 是 grandfather 的左边
    44. // g
    45. // p
    46. Node* uncle = grandfather->_right;
    47. // 如果叔叔存在,且叔叔的颜色为红色
    48. // 那么就将叔叔和父亲的颜色全都改为黑色,然后将祖父的颜色改为红色
    49. if (uncle && uncle->_col == RED)
    50. {
    51. parent->_col = uncle->_col = BLACK;
    52. grandfather->_col = RED;
    53. //向上迭代
    54. cur = grandfather;
    55. parent = cur->_parent;
    56. }
    57. else
    58. {
    59. // 叔叔不存在或者叔叔存在但是叔叔的颜色为黑色
    60. if (cur == parent->_left)
    61. {
    62. // g
    63. // p
    64. // c
    65. //右单旋
    66. RotateR(grandfather);
    67. //将父亲的节点变为黑色,祖父的节点变为红色
    68. parent->_col = BLACK;
    69. grandfather->_col = RED;
    70. break;
    71. }
    72. else
    73. {
    74. // g
    75. // p
    76. // c
    77. //左右双旋
    78. RotateL(parent);
    79. RotateR(grandfather);
    80. //将cur 的颜色变为黑色,祖父的节点变为红色
    81. cur->_col = BLACK;
    82. grandfather->_col = RED;
    83. break;
    84. }
    85. }
    86. }
    87. else
    88. {
    89. // parent 是 grandfather 的右边
    90. // g
    91. // p
    92. Node* uncle = grandfather->_left;
    93. // 如果叔叔存在,且叔叔的颜色为红色
    94. // 那么就将叔叔和父亲的颜色全都改为黑色,然后将祖父的颜色改为红色
    95. if (uncle && uncle->_col == RED)
    96. {
    97. parent->_col = uncle->_col = BLACK;
    98. grandfather->_col = RED;
    99. //向上迭代
    100. cur = grandfather;
    101. parent = cur->_parent;
    102. }
    103. else
    104. {
    105. // 叔叔不存在或者叔叔存在但是叔叔的颜色为黑色
    106. if (cur == parent->_right)
    107. {
    108. // g
    109. // p
    110. // c
    111. //左单旋
    112. RotateL(grandfather);
    113. //将父亲的节点变为黑色,祖父的节点变为红色
    114. parent->_col = BLACK;
    115. grandfather->_col = RED;
    116. break;
    117. }
    118. else
    119. {
    120. // g
    121. // p
    122. // c
    123. //右左双旋
    124. RotateR(parent);
    125. RotateL(grandfather);
    126. //将cur 的颜色变为黑色,祖父的节点变为红色
    127. cur->_col = BLACK;
    128. grandfather->_col = RED;
    129. break;
    130. }
    131. }
    132. }
    133. }
    134. _root->_col = BLACK;
    135. return true;
    136. }

    而旋转,我们以及在 AVL 就以及说过了,树的旋转是搜索树的旋转规则,并不是AVL 树或者红黑树特有的,所以旋转是通用的,只是红黑树的旋转不需要维持平衡因子,只需要旋转即可。

    左单旋

    1. // 左单旋
    2. void RotateL(Node* parent)
    3. {
    4. Node* cur = parent->_right;
    5. Node* curLeft = cur->_left;
    6. parent->_right = curLeft;
    7. if (curLeft)
    8. {
    9. curLeft->_parent = parent;
    10. }
    11. cur->_left = parent;
    12. Node* pparent = parent->_parent;
    13. parent->_parent = cur;
    14. if (pparent == nullptr)
    15. {
    16. // parent 就是根节点
    17. _root = cur;
    18. cur->_parent = nullptr;
    19. }
    20. else
    21. {
    22. if (parent == pparent->_left)
    23. {
    24. pparent->_left = cur;
    25. }
    26. else
    27. {
    28. pparent->_right = cur;
    29. }
    30. cur->_parent = pparent;
    31. }
    32. }

    右单旋

    1. //右单旋
    2. void RotateR(Node* parent)
    3. {
    4. Node* cur = parent->_left;
    5. Node* curRight = cur->_right;
    6. parent->_left = curRight;
    7. if (curRight)
    8. {
    9. curRight->_parent = parent;
    10. }
    11. cur->_right = parent;
    12. Node* pparent = parent->_parent;
    13. parent->_parent = cur;
    14. if (pparent == nullptr)
    15. {
    16. // 说明 parent 是根节点
    17. _root = cur;
    18. cur->_parent = nullptr;
    19. }
    20. else
    21. {
    22. if (parent == pparent->_left)
    23. {
    24. pparent->_left = cur;
    25. }
    26. else
    27. {
    28. pparent->_right = cur;
    29. }
    30. cur->_parent = pparent;
    31. }
    32. }

  • 相关阅读:
    Linux线程同步:深度解析条件变量接口
    【LeetCode】16. RansomNode·赎金信
    Vue2.0新手入门-模板语法-计算属性与监听属性的介绍和差异
    python实现udp通信代码
    生产者消费者模式
    极智AI | 昇腾 CANN ATC 模型转换
    【SpringBoot】配置文件.properties和.yml
    matlab新建数据字典及如何导入
    SmartIDE v0.1.16 已经发布 - 支持阿里&蚂蚁开源的国产 IDE OpenSumi
    模块化与单片化优缺点解析:为什么单片链仍是 DeFi 协议的最好选择?
  • 原文地址:https://blog.csdn.net/m0_73455775/article/details/132798884