• 可怕的红黑树


    目录

    1.红黑树介绍

    1.1概念

     1.2.红黑树的性质

    1.3.红黑树节点的定义

    2.红黑树的旋转

    2.1右单旋

    2.2左单旋

    3.红黑树的插入

     3.1插入情况一

    3.2插入情况2

    3.3插入情况3

    3.4情况总结

    4.红黑树的验证

    5.红黑树与AVL树的比较


    1.红黑树介绍

    1.1概念

    红黑树 ,是一种 二叉搜索树 ,但 在每个结点上增加一个存储位表示结点的颜色,可以是 Red
    Black 。 通过对 任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路
    径会比其他路径长出俩倍 ,因而是 接近平衡 的。

    如图:

     1.2.红黑树的性质

    1. 1. 每个结点不是红色就是黑色
    2. 2. 根节点是黑色的 
    3. 3. 如果一个节点是红色的,则它的两个孩子结点是黑色的 
    4. 4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点 
    5. 5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
    最短路径全都是黑色节点构成即长度为N.
    最长路径则是由红色和黑色交替构成,在该路径中红色节点和黑树节点的个数相同,我们就可以得到最长路径的长度为2N,所以在黑树中最长路径不会超过最短路径的2倍,从而达到了近似平衡。

    1.3.红黑树节点的定义

    1. template<class K, class V>
    2. struct RBTreeNode
    3. {
    4. RBTreeNode<K, V>* _left;
    5. RBTreeNode<K, V>* _right;
    6. RBTreeNode<K, V>* _parent;
    7. pair<K, V> _kv;
    8. Colour _col;
    9. RBTreeNode(const pair<K, V>& kv)
    10. :_kv(kv)
    11. , _left(nullptr)
    12. , _right(nullptr)
    13. , _parent(nullptr)
    14. , _col(RED)
    15. {}
    16. };

    2.红黑树的旋转

    旋转其实与AVL树的旋转是一样的,也是分为左右单旋,和左右.右左双旋。

    2.1右单旋

     代码:

    1. void RotateL(Node* parent)
    2. {
    3. Node* ppNode = parent->_parent;//先存储父亲结点的父亲结点
    4. Node* subR = parent->_right;
    5. Node* subRL = subR->_left;
    6. parent->_right = subRL;
    7. subR->_left = parent;
    8. if(subRL) //subRL可能为空
    9. subRL->_parent = parent;
    10. parent->_parent = subR;
    11. if (parent == _root) //如果parent是根节点
    12. {
    13. _root = subR;
    14. _root->_parent = nullptr;
    15. }
    16. else //如果parent不是根节点
    17. {
    18. if (parent == ppNode->_left)//如果parent在其父亲结点左侧
    19. {
    20. ppNode->_left = subR;
    21. }
    22. else //如果parent在其父亲结点右侧
    23. {
    24. ppNode->_right = subR;
    25. }
    26. subR->_parent = ppNode;
    27. }
    28. }

    2.2左单旋

     对应代码

    1. void RotateR(Node* parent)
    2. {
    3. Node* ppNode = parent->_parent;
    4. Node* subL = parent->_left;
    5. Node* subLR = subL->_right;
    6. subL->_right = parent;
    7. parent->_left = subLR;
    8. if (subLR)
    9. subLR->_parent = parent;
    10. parent->_parent = subL;
    11. if (parent == _root)
    12. {
    13. _root = subL;
    14. _root->_parent = nullptr;
    15. }
    16. else
    17. {
    18. if (ppNode->_left == parent)
    19. {
    20. ppNode->_left = subL;
    21. }
    22. else
    23. {
    24. ppNode->_right = subL;
    25. }
    26. subL->_parent = ppNode;
    27. }
    28. }

    3.红黑树的插入

    要插入一个节点我们是希望他是红色节点还是黑树节点?很显然是红色节点,如果插入的是黑色节点,这会破坏红黑树中任何一条路径中黑色节点的数量相同。对应这种情况我们对红黑树进行调整就变得很麻烦。因此我们希望插入的节点为红色节点。

    先简单定义下概念:把要插入的结点定义为cur,父亲结点定义为p,父亲结点的父亲结点定义为g,其叔叔定义为u.

    如图:

    主要根据叔叔结点分情况讨论

     3.1插入情况一

    cur 为红, p 为红, g 为黑, u 存在且为红

    将p,u结点变黑,如果g不是根结点,把其变红,然后再向上调整。

    对应代码实现(假设cur为p的左孩子,p为g的左孩子):

    1. while (parent && parent->_col == RED)
    2. {
    3. Node* grandfater = parent->_parent;
    4. assert(grandfater);
    5. if (grandfater->_left == parent)
    6. {
    7. Node* uncle = grandfater->_right;
    8. if (uncle && uncle->_col == RED) // 叔叔存在且为红
    9. {
    10. parent->_col = uncle->_col = BLACK;
    11. grandfater->_col = RED;
    12. cur = grandfater;
    13. parent = cur->_parent;
    14. }
    15. }
    16. }

    3.2插入情况2

      cur 为红, p 为红, g 为黑, u 不存在 /u 存在且为黑 
    先看最简单的情况:u不存在

     这种情况直接进行右旋就可以。可直接调用上面右旋的代码

    1. while (parent && parent->_col == RED)
    2. {
    3. Node* grandfater = parent->_parent;
    4. assert(grandfater);
    5. if (grandfater->_left == parent)
    6. {
    7. Node* uncle = grandfater->_right;
    8. if (uncle && uncle->_col == RED) // 叔叔存在且为红
    9. {
    10. parent->_col = uncle->_col = BLACK;
    11. grandfater->_col = RED;
    12. cur = grandfater;
    13. parent = cur->_parent;
    14. }
    15. else // 叔叔不存在 或者 叔叔存在且为黑
    16. {
    17. if (cur == parent->_left) // 单旋
    18. {
    19. RotateR(grandfater);//进行左单旋
    20. parent->_col = BLACK;
    21. grandfater->_col = RED;
    22. }
    23. break;
    24. }
    25. }
    26. }

    如果u存在且为黑呢?如图:

     一样的是以g为旋转点进行右旋。

    补充:

     pg的左孩子,curp的左孩子,则进行右单旋转;

     pg的右孩子,curp的右孩子,则进行左单旋转

    pg变色--p变黑,g变红(g不是根节点的情况下)

    3.3插入情况3

    cur为红,p为红,g为黑,u不存在/u存在且为黑 ,但 1--:pg的左孩子,curp的右孩子,2--:pg的右孩子,curp的左孩子。

    先看一种最简单的情况,u不存在,p为g的左孩子,cur为p的右孩子,如图:

     对p进行左单旋,也就变成了情况二中的情况,再对g进行右单旋即可,然后再更改颜色。

    如果u存在且为黑呢?

     思路是一样的,代码实现:

    1. while (parent && parent->_col == RED)
    2. {
    3. Node* grandfater = parent->_parent;
    4. assert(grandfater);
    5. if (grandfater->_left == parent)
    6. {
    7. Node* uncle = grandfater->_right;
    8. if (uncle && uncle->_col == RED) // 叔叔存在且为红
    9. {
    10. parent->_col = uncle->_col = BLACK;
    11. grandfater->_col = RED;
    12. cur = grandfater;
    13. parent = cur->_parent;
    14. }
    15. else // 叔叔不存在 或者 叔叔存在且为黑
    16. {
    17. if (cur == parent->_left) // 单旋,parent为grandfather的左,cur为parent的左
    18. {
    19. RotateR(grandfater);//进行左单旋
    20. parent->_col = BLACK;
    21. grandfater->_col = RED;
    22. }
    23. else // 双旋parent为grandfather的左,cur为parent的右
    24. {
    25. RotateL(parent);//对parent进行左单旋
    26. RotateR(grandfater);//对grandfather进行右单旋
    27. cur->_col = BLACK;
    28. grandfater->_col = RED;
    29. }
    30. break;
    31. }
    32. }
    33. }

    以上情况p都为grandfather的左孩子进行演示的。

    完整插入代码实现:

    1. bool Insert(const pair<K, V>& kv)
    2. {
    3. if (_root == nullptr)//如果是空树,直接插入
    4. {
    5. _root = new Node(kv);
    6. _root->_col = BLACK;
    7. return true;
    8. }
    9. Node* parent = nullptr;
    10. Node* cur = _root;
    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. return false;
    26. }
    27. }
    28. cur = new Node(kv);
    29. cur->_col = RED;
    30. if (parent->_kv.first < kv.first)
    31. {
    32. parent->_right = cur;
    33. }
    34. else
    35. {
    36. parent->_left = cur;
    37. }
    38. cur->_parent = parent;
    39. //*******************************************************************
    40. //插入节点成功,下面对节点进行调整
    41. while (parent && parent->_col == RED)
    42. {
    43. Node* grandfater = parent->_parent;
    44. assert(grandfater);
    45. if (grandfater->_left == parent) //p为g的左孩子
    46. {
    47. Node* uncle = grandfater->_right; //u为g的右孩子
    48. if (uncle && uncle->_col == RED) // 叔叔存在且为红。情况一
    49. {
    50. parent->_col = uncle->_col = BLACK;
    51. grandfater->_col = RED;
    52. cur = grandfater;
    53. parent = cur->_parent;
    54. }
    55. else // 叔叔不存在 或者 叔叔存在且为黑
    56. {
    57. if (cur == parent->_left) // 单旋,p为g的左孩子,cur为p的左孩子
    58. {
    59. RotateR(grandfater);
    60. parent->_col = BLACK;
    61. grandfater->_col = RED;
    62. }
    63. else // 双旋,p为g的左孩子,cur为p的右孩子
    64. {
    65. RotateL(parent);
    66. RotateR(grandfater);
    67. cur->_col = BLACK;
    68. grandfater->_col = RED;
    69. }
    70. break;
    71. }
    72. }
    73. else //(grandfater->_right == parent),p为g的右孩子
    74. {
    75. Node* uncle = grandfater->_left;// u为g的左孩子
    76. if (uncle && uncle->_col == RED)
    77. {
    78. parent->_col = uncle->_col = BLACK;
    79. grandfater->_col = RED;
    80. cur = grandfater;
    81. parent = cur->_parent;
    82. }
    83. else
    84. {
    85. if (cur == parent->_right)
    86. {
    87. RotateL(grandfater);
    88. parent->_col = BLACK;
    89. grandfater->_col = RED;
    90. }
    91. else
    92. {
    93. RotateR(parent);
    94. RotateL(grandfater);
    95. cur->_col = BLACK;
    96. grandfater->_col = RED;
    97. }
    98. break;
    99. }
    100. }
    101. }
    102. _root->_col = BLACK;//把根节点变为黑色
    103. return true;
    104. }

    3.4情况总结

    红黑树插入后,如何进行调整主要看u(叔叔)节点,以及cur与p节点的关系

    1--u存在且为红:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整。

    2--cur为红,p为红,g为黑,u不存在/u存在且为黑:p为g的左孩子,cur为p的左孩子,则进行右单旋转;p为g的右孩子,cur为p的右孩子,则进行左单旋转 .p、g变色--p变黑,g变红

    3--cur为红,p为红,g为黑,u不存在/u存在且为黑:p为g的左孩子,cur为p的右孩子,则进行左单旋转;p为g的右孩子,cur为p的左孩子,则进行右单旋转 .这样变为了情况2.

    4.红黑树的验证

    如何验证上面的代码生成的是一颗红黑树呢?

    红黑树的检测分为两步:
    1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
    2. 检测其是否满足红黑树的性质。
    第二步如何检测呢?
    1:不存在连续的红节点  2.每条路径上的黑色节点数目是相同的。3.根节点为黑色
    例如:

     这颗红黑树一共有13条路径,需要检测每条路径是否存在连续的红节点,以及每条路径上黑节点的个数是否相同。可以先算出最右边路径上黑节点的数目,然后从根节点递归检测

    中序遍历代码:

    1. void _InOrder(Node* root)
    2. {
    3. if (root == nullptr)
    4. return;
    5. _InOrder(root->_left);
    6. cout << root->_kv.first << " ";
    7. _InOrder(root->_right);
    8. }

    检测每一条路径:

    1. bool IsBalanceTree()
    2. {
    3. Node* pRoot = _root;
    4. if (nullptr == pRoot)//空树也是红黑树
    5. return true;
    6. if (BLACK != pRoot->_col)//检查根节点
    7. {
    8. cout << "违反红黑树性质二:根节点必须为黑色" << endl;
    9. return false;
    10. }
    11. size_t blackCount = 0; // 获取任意一条路径中黑色节点的个数 -- 比较基准值
    12. Node* pCur = pRoot;
    13. while (pCur)
    14. {
    15. if (BLACK == pCur->_col)
    16. blackCount++;
    17. pCur = pCur->_right;
    18. }
    19. // 检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数
    20. size_t k = 0;
    21. return _IsValidRBTree(pRoot, k, blackCount);
    22. }
    23. bool _IsValidRBTree(Node* pRoot, size_t k, const size_t blackCount)
    24. {
    25. if (nullptr == pRoot)//走到null之后,判断k和black是否相等
    26. {
    27. if (k != blackCount)
    28. {
    29. cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;
    30. return false;
    31. }
    32. return true;
    33. }
    34. // 统计黑色节点的个数
    35. if (BLACK == pRoot->_col)
    36. k++;
    37. // 检测当前节点与其双亲是否都为红色
    38. if (RED == pRoot->_col && pRoot->_parent && pRoot->_parent->_col == RED)
    39. {
    40. cout << "违反性质三:存在连在一起的红色节点" << endl;
    41. return false;
    42. }
    43. return _IsValidRBTree(pRoot->_left, k, blackCount) &&
    44. _IsValidRBTree(pRoot->_right, k, blackCount);
    45. }

    5.红黑树与AVL树的比较

    • AVL树是通过控制左右高度差不超过1来实现二叉树平衡的,实现的是二叉树的严格平衡。
    • 红黑树是通过控制结点的颜色,从而使得红黑树当中最长可能路径不超过最短可能路径的2倍,实现的是近似平衡。
    • 红黑树和 AVL 树都是高效的平衡二叉树,增删改查的时间复杂度都是 O($log_2 N$) ,红黑树不追 求绝对平衡,其只需保证最长路径不超过最短路径的 2 倍,相对而言,降低了插入和旋转的次数, 所以在经常进行增删的结构中性能比 AVL 树更优,而且红黑树实现比较简单,所以实际运用中红 黑树更多。
  • 相关阅读:
    肥泽-酒店辅助订购系统
    linux 文件系统命令
    使用单调栈解决 “下一个更大元素” 问题
    零食食品经营小程序商城的作用是什么
    js逆向播放量增加,增加视频热度,uuid,sid,buvid3,aid,b_lsid, b_nut 还原实现过程
    华为OD机试真题 Java 实现【数组二叉树】【2023 B卷 200分】,附详细解题思路
    mathtype符号显示不全的问题
    ​C++基础入门 指针
    宁德时代打响增长保卫战
    阶段性检测实战项目----文件搜索引擎
  • 原文地址:https://blog.csdn.net/m0_64397669/article/details/126452393