• C++模拟实现——红黑树


    一、介绍

    红黑树也是对一般的搜索二叉树不能保证平衡的一个改进,和AVL树采用的思路不同,但同样需要旋转,其本质也是一颗平衡搜索二叉树,其节点有颜色的区分,并且被一些规则束缚,在这些规则下,能够使得树最长路径的长度不会高于最短路径的两倍

    二、红黑树的性质

    1.红黑树的节点,不是红色,就是黑色

    2.根节点是黑色的

    3.路径上不能出现两个连续的红色节点

    4.每条路径上的黑色节点数量相同

    5.每个叶子节点指向的空节点,默认认为是黑色的

    遵循以上规则,则可以保证最长的路径的长度不会超过最短路径的两倍,因此红黑树实现平衡的核心,就是对新插入的节点使其通过一系列操作,满足上面的五个条件即可

    三、红黑树的定义

    插入的节点默认为红色,是为了能够调整,插入红色,可能只需要局部调整,若是黑色,则每次插入都必然会影响所有路径

    四、红黑树的核心实现Insert

    1.基本框架

    先是插入数据,然后再是根据规则做出调整,红黑树实现的核心就在于如何调整,使得各种情况的插入都可以调整成符合规则的样子

    2.调整分析

    情况一:当插入一个新的节点cur为根节点,则直接插入,并且将颜色改为黑色即可
    (根节点为黑色)

    情况二:继续插入,若是parent节点为黑色,则插入一个红色节点不会破坏规则,因此无需调整
    (不能有连续的红色节点,每个路径黑色节点相同)

    情况三:插入cur节点,其parent节点也是红色,则出现了两个连续的红色节点,需要根据不同情况进行分类调整:

    (1).uncle存在且为红

    处理:将parent和uncle变黑,将grandfather变红,然后继续向上调整,cur指向g
    parent指向g->_parent

    p和u同时变黑,使得g左右两边路径同时增加了一个黑色节点,因此需要将g变红,这样既不影响黑色节点的规则,也没有连续出现的红色节点,但是由于g变红,因此可能会对上面部分造成影响,所以需要继续向上调整,将cur指向grandfather,parent指向g的parent往上继续检查

    (2)uncle不存在或者存在且为黑

    处理:根据具体情况进行旋转(单旋、双旋),旋转后再将局部最上方的变黑,grandfather变红

    旋转即降了高度,并且旋转后通过调整颜色,使得局部内同时符合黑色和红色的约束条件

    需要特殊处理的就只有这两种情况,但在代码实现的角度,需要对这两种情况进行细分,首先是先判断parent在grandfather的左边还是右边,这个影响着旋转往哪边旋,然后再是分“叔叔存在且为红”和“叔叔不在或者在且为黑”这两种情况分类讨论,但是处理的思路是一样的,只是在细节上要根据具体情况进行调整,在“叔叔不在或者在且为黑”的条件下,需要继续细分cur在parent的左边还是右边,确定具体是单旋还是双旋,旋转后再变色即可

    3.代码实现部分分析

    4.具体代码

    1. bool Insert(const T& data)
    2. {
    3. if (_root == nullptr)
    4. {
    5. _root = new Node(data);
    6. _root->_col = BLACK;
    7. return true;
    8. }
    9. Node* cur = _root;
    10. Node* parent = nullptr;
    11. while (cur)
    12. {
    13. if (data < cur->_data)
    14. {
    15. parent = cur;
    16. cur = cur->_left;
    17. }
    18. else if (data > cur->_data)
    19. {
    20. parent = cur;
    21. cur = cur->_right;
    22. }
    23. else if (data == cur->_data)
    24. {
    25. return false;
    26. }
    27. }
    28. cur = new Node(data);
    29. if (data < parent->_data)
    30. {
    31. parent->_left = cur;
    32. }
    33. else
    34. {
    35. parent->_right = cur;
    36. }
    37. cur->_parent = parent;
    38. //插入完成后检查是否需要调整
    39. while (parent && parent->_col == RED)
    40. {
    41. Node* grandfather = parent->_parent;
    42. if (grandfather->_left == parent)//情况一,叔叔存在且为红
    43. {
    44. Node* uncle = grandfather->_right;
    45. if (uncle && uncle->_col == RED)
    46. {
    47. parent->_col = BLACK;
    48. uncle->_col = BLACK;
    49. grandfather->_col = RED;
    50. cur = grandfather;
    51. parent = grandfather->_parent;
    52. }
    53. else//叔叔不存在或者存在为黑
    54. {
    55. if (parent->_left == cur)//情况二
    56. {
    57. RotateR(grandfather);
    58. grandfather->_col = RED;
    59. parent->_col = BLACK;
    60. }
    61. else//情况三
    62. {
    63. RotateL(parent);
    64. RotateR(grandfather);
    65. cur->_col = BLACK;
    66. grandfather->_col = RED;
    67. }
    68. break;
    69. }
    70. }
    71. else//grandfather->right == parent
    72. {
    73. Node* uncle = grandfather->_left;
    74. if (uncle && uncle->_col == RED)
    75. {
    76. parent->_col = BLACK;
    77. uncle->_col = BLACK;
    78. grandfather->_col = RED;
    79. cur = grandfather;
    80. parent = grandfather->_parent;
    81. }
    82. else
    83. {
    84. if (parent->_right == cur)//情况二
    85. {
    86. RotateL(grandfather);
    87. parent->_col = BLACK;
    88. grandfather->_col = RED;
    89. }
    90. else
    91. {
    92. RotateR(parent);
    93. RotateL(grandfather);
    94. cur->_col = BLACK;
    95. grandfather->_col = RED;
    96. }
    97. break;
    98. }
    99. }
    100. }
    101. //调整完后,将根重新置为黑色,避免某次调整到根时,将根变成了红色
    102. _root->_col = BLACK;
    103. return true;
    104. }

    五、测试接口

    在实现完Insert接口后,我们需要实现一些用于测试的接口,验证是否为红黑树

    中序遍历InOrder

    首先是提供一个中序遍历,但中序遍历只能验证是否是搜索二叉树,并不能保证一定是红黑树

    1. void InOrder()
    2. {
    3. _InOrder(_root);
    4. cout << endl;
    5. }
    6. void _InOrder(const Node* root)
    7. {
    8. if (root == nullptr)
    9. {
    10. return;
    11. }
    12. _InOrder(root->_left);
    13. cout << root->_data << " ";
    14. _InOrder(root->_right);
    15. }

    验证红黑树IsBalance

    验证是否是红黑树,取决于树是否遵循着红黑树的规则,因此我们需要根据规则去写个函数去检查树是否为红黑树

    1. //测试是否为红黑树
    2. bool IsBalance()
    3. {
    4. if (_root->_col == RED)
    5. {
    6. return false;
    7. }
    8. int Reference = -1;
    9. return _Check(_root,0,Reference);
    10. }
    11. //1.不能有连续的红色节点
    12. //2.每条路径的黑色节点要数量相同
    13. bool _Check(const Node* root,int black_num,int& Reference)
    14. {
    15. if (root == nullptr)
    16. {
    17. if (Reference == -1)//由第一次走完的路径作为参考值
    18. {
    19. Reference = black_num;
    20. }
    21. else if (Reference != black_num)//当存在黑色节点数量与参考值不同的路径时,说明违反规则
    22. {
    23. return false;
    24. }
    25. return true;
    26. }
    27. //当如果节点为红,则检查父母节点是否也为红,若是红则违反规则
    28. if (root->_col == RED && root->_parent && root->_parent->_col == RED)
    29. {
    30. return false;
    31. }
    32. //统计每条路径的黑色节点,当走到空时则说明该路径走完
    33. if (root->_col == BLACK)
    34. {
    35. black_num++;
    36. }
    37. return _Check(root->_left,black_num,Reference) && _Check(root->_right,black_num,Reference);
    38. }
    39. };

    总结

    本章模拟实现了红黑树的核心部分,提供了测试接口,下一篇将会把红黑树进行一些改造,并且完整红黑树的部分基本接口,用于封装set和map,并且将模拟实现对set和map的封装

  • 相关阅读:
    LQ0170 抽签【组合】
    JSON数据获取指南!
    微光互联 TX800-U 扫码器无法输出中文到光标的问题
    Web前端:6个最佳在线跨浏览器测试工具(免费和付费)
    15-基于Nginx构建Tomcat集群
    Centos系统常见配置(详细)总结
    华为Mate系列回归,高端市场洗牌开始
    redis进阶
    【Pytorch】Pytorch学习笔记02 - 单变量时间序列 LSTM
    基于promiseAPlus 实现 Promise
  • 原文地址:https://blog.csdn.net/china_chk/article/details/134389242