• 【数据结构】红黑树


    目录

    红黑树的概念

    红黑树的性质

    红黑树结点的定义

    红黑树的插入

    平衡化操作

    情形一:uncle结点存在且为红

    情形二:uncle结点不存在或者存在且为黑

    uncle结点不存在

    uncle结点存在且为黑

    左旋代码实现

    右旋代码实现

    Insert()函数代码实现

    红黑树的验证


    红黑树的概念

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

    红黑树的性质

    性质一: 每个结点不是红色就是黑色 ;

    性质二: 根节点是黑色的;

    性质三: 如果一个节点是红色的,则它的两个孩子结点必须是黑色的(即没有连续的红色结点) 

    性质四: 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点(即每条路径都包含相同数量的黑色结点)

    性质五:每个叶子结点都是黑色的(此处的叶子结点指的是空结点);

    思考一:为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?

    当某条路径最短时,这条路径上所有节点必然由黑色节点构成,由于性质四限定了从任一节点到其每个叶子节点的所有路径必须包含相同数量的黑色节点,所以最长路径上的黑节点的数目和最短路径上的黑节点的数目相等,由于性质三限定不能出现两个连续的红色节点,因此最长路径必然是由红色和黑色节点交替构成;

    最短路径:全黑                                    最长路径:一黑一红

    思考二:节点的定义中,需要将节点的默认颜色设置为黑色还是红色?

    若将节点的默认颜色设置为黑色,一定会违反红黑树的性质四:从任一节点到其每个叶子节点的所有路径必须包含相同数量的黑色节点;

    若将节点的默认颜色设置为红色,若插入位置的父节点为黑色,未违反红黑树性质,则插入结束;若插入位置的父节点为红色,则出现连续的红色结点违反红黑树性质三,需要处理;

    因此结点的默认颜色设置为红色

    红黑树结点的定义

    1. enum Color
    2. {
    3. RED,
    4. BLACK
    5. };
    6. template<class k,class v>
    7. struct RBTreeNode
    8. {
    9. RBTreeNode* _left;//指向左孩子
    10. RBTreeNode* _right;//指向右孩子
    11. RBTreeNode* _parent;//指向父节点
    12. Color _col;//结点颜色
    13. pair _kv;//存储有效数据
    14. //节点类的构造函数
    15. RBTreeNode(const pair& kv)
    16. :_left(nullptr)
    17. , _right(nullptr)
    18. , _parent(nullptr)
    19. , _col(RED)
    20. , _kv(kv)
    21. {}
    22. };

    红黑树的插入

    红黑树的插入分为两步:

    1. 按照搜索二叉树的规则插入新节点(红色节点);
    2. 检测插入红色结点后,若插入位置的父节点为黑色,未违反红黑树性质,则插入结束;检测插入红色结点后,若插入位置的父节点为红色,需要对红黑树进行平衡化操作;
    1. template <class k, class v>
    2. class RBTree
    3. {
    4. typedef RBTreeNode Node;
    5. public:
    6. //首先按照搜索二叉树的规则插入数据
    7. bool Insert(const pair& kv)
    8. {
    9. if (_root == nullptr)
    10. {
    11. _root = new Node(kv);
    12. //红黑树根结点为黑色
    13. _root->_col = BLACK;
    14. return true;
    15. }
    16. //parent记录父节点的位置,cur寻找插入位置
    17. Node* parent = nullptr;
    18. Node* cur = _root;
    19. while (cur != nullptr)
    20. {
    21. //根据键值key比较,确定迭代左子树或是右子树
    22. if ((cur->_kv).first < kv.first)
    23. {
    24. parent = cur;
    25. cur = cur->_right;
    26. }
    27. else if ((cur->_kv).first>kv.first)
    28. {
    29. parent = cur;
    30. cur = cur->_left;
    31. }
    32. //为维护搜索二叉树逻辑结构,同一颗树不能出现相等的数值
    33. else
    34. {
    35. return false;
    36. }
    37. }
    38. //开辟结点,存储数据,建立链接关系
    39. cur = new Node(kv);
    40. if ((parent->_kv).first > kv.first)
    41. {
    42. parent->_left = cur;
    43. }
    44. else
    45. {
    46. parent->_right = cur;
    47. }
    48. cur->_parent = parent;
    49. //平衡化操作
    50. //...
    51. return true;
    52. }
    53. private:
    54. Node* _root = nullptr;
    55. };

    平衡化操作

    规定

    cur 为新增节点,p (parent)为父节点,g (grandfather)为祖父节点,u (uncle)为叔叔节点;

    情形一:uncle结点存在且为红

    具象图:

    若调整过程中cur的父结点p为空,说明cur为根节点,已经调整到根结点处,此时将根节点改为黑色;

     情形二的处理方式为旋转加变色(稍后讨论),情形一向上迭代的过程中可能会产生情形二

    抽象图:

    无论父亲p/叔叔u是爷爷g的左孩子还是右孩子,无论cur是父亲p的左孩子还是右孩子,情形一的处理方式相同,对变色毫无影响;

    情形二:uncle结点不存在或者存在且为黑

    uncle结点不存在

    case 1p为g的左孩子,cur为p的左孩子,进行右单旋,p变黑,g变红

     case 2p为g的右孩子,cur为p的右孩子,进行左单旋,p变黑,g变红

    case 3:p为g的左孩子,cur为p的右孩子,进行左右双旋,cur变黑,g变红;

    case 4:p为g的右孩子,cur为p的左孩子,进行右左双旋,cur变黑,g变红;

    uncle结点存在且为黑

    case 1:p为g的左孩子,cur为p的左孩子,进行右单旋,p变黑,g变红

    case 2:p为g的右孩子,cur为p的右孩子,进行左单旋,p变黑,g变红

    case 3:p为g的左孩子,cur为p的右孩子,进行左右双旋,cur变黑,g变红;

    case 4:p为g的右孩子,cur为p的左孩子,进行右左双旋,cur变黑,g变红;

    抽象图:

    左旋代码实现
    1. void RotateL(Node* parent)
    2. {
    3. Node* subR = parent->_right;
    4. Node* subRL = subR->_left;
    5. parent->_right = subRL;
    6. if (subRL)
    7. subRL->_parent = parent;
    8. subR->_left = parent;
    9. Node* ppnode = parent->_parent;
    10. parent->_parent = subR;
    11. if (parent == _root)
    12. {
    13. _root = subR;
    14. subR->_parent = nullptr;
    15. }
    16. else
    17. {
    18. if (ppnode->_left == parent)
    19. {
    20. ppnode->_left = subR;
    21. }
    22. else
    23. {
    24. ppnode->_right = subR;
    25. }
    26. subR->_parent = ppnode;
    27. }
    28. }
    右旋代码实现
    1. void RotateR(Node* parent)
    2. {
    3. Node* subL = parent->_left;
    4. Node* subLR = subL->_right;
    5. parent->_left = subLR;
    6. if (subLR)
    7. subLR->_parent = parent;
    8. subL->_right = parent;
    9. Node* ppnode = parent->_parent;
    10. parent->_parent = subL;
    11. if (parent == _root)
    12. {
    13. _root = subL;
    14. subL->_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. }
    Insert()函数代码实现
    1. //首先按照搜索二叉树的规则插入数据
    2. bool Insert(const pair& kv)
    3. {
    4. if (_root == nullptr)
    5. {
    6. _root = new Node(kv);
    7. //红黑树根结点为黑色
    8. _root->_col = BLACK;
    9. return true;
    10. }
    11. //parent记录父节点的位置,cur寻找插入位置
    12. Node* parent = nullptr;
    13. Node* cur = _root;
    14. while (cur != nullptr)
    15. {
    16. //根据键值key比较,确定迭代左子树或是右子树
    17. if ((cur->_kv).first < kv.first)
    18. {
    19. parent = cur;
    20. cur = cur->_right;
    21. }
    22. else if ((cur->_kv).first>kv.first)
    23. {
    24. parent = cur;
    25. cur = cur->_left;
    26. }
    27. //为维护搜索二叉树逻辑结构,同一颗树不能出现相等的数值
    28. else
    29. {
    30. return false;
    31. }
    32. }
    33. //开辟结点,存储数据,建立链接关系
    34. cur = new Node(kv);
    35. if ((parent->_kv).first > kv.first)
    36. {
    37. parent->_left = cur;
    38. }
    39. else
    40. {
    41. parent->_right = cur;
    42. }
    43. cur->_parent = parent;
    44. //平衡化操作
    45. while (parent && parent->_col == RED)
    46. {
    47. Node* grandfather = parent->_parent;
    48. if (parent == grandfather->_left)
    49. {
    50. Node* uncle = grandfather->_right;
    51. // 情况一:叔叔存在且为红
    52. if (uncle && uncle->_col == RED)
    53. {
    54. // 变色
    55. parent->_col = uncle->_col = BLACK;
    56. grandfather->_col = RED;
    57. // 继续往上处理
    58. cur = grandfather;
    59. parent = cur->_parent;
    60. }
    61. else
    62. {
    63. // 情况二:叔叔不存在或者存在且为黑
    64. // 旋转+变色
    65. if (cur == parent->_left)
    66. {
    67. // g
    68. // p u
    69. // c
    70. RotateR(grandfather);
    71. parent->_col = BLACK;
    72. grandfather->_col = RED;
    73. }
    74. else
    75. {
    76. // g
    77. // p u
    78. // c
    79. RotateL(parent);
    80. RotateR(grandfather);
    81. cur->_col = BLACK;
    82. grandfather->_col = RED;
    83. }
    84. break;
    85. }
    86. }
    87. //parent==grandfather->_right
    88. else
    89. {
    90. Node* uncle = grandfather->_left;
    91. // 情况一:叔叔存在且为红
    92. if (uncle && uncle->_col == RED)
    93. {
    94. // 变色
    95. parent->_col = uncle->_col = BLACK;
    96. grandfather->_col = RED;
    97. // 继续往上处理
    98. cur = grandfather;
    99. parent = cur->_parent;
    100. }
    101. else
    102. {
    103. // 情况二:叔叔不存在或者存在且为黑
    104. // 旋转+变色
    105. // g
    106. // u p
    107. // c
    108. if (cur == parent->_right)
    109. {
    110. RotateL(grandfather);
    111. parent->_col = BLACK;
    112. grandfather->_col = RED;
    113. }
    114. else
    115. {
    116. // g
    117. // u p
    118. // c
    119. RotateR(parent);
    120. RotateL(grandfather);
    121. cur->_col = BLACK;
    122. grandfather->_col = RED;
    123. }
    124. break;
    125. }
    126. }
    127. }
    128. _root->_col = BLACK;
    129. return true;
    130. }

    红黑树的验证

         红黑树的检测分为两步:

    • 检测其是否满足搜索二叉树(中序遍历是否为有序序列)

    • 检测其是否满足红黑树的性质

      1. 根节点是否为黑色;

      2. 是否存在连续红节点;

      3. 统计每条路径上的黑节点数是否相等;

    1. //红黑树的检测
    2. //中序遍历检测是否满足搜索二叉树的性质
    3. void _InOrder(Node* root)
    4. {
    5. if (root == nullptr)
    6. return;
    7. _InOrder(root->_left);
    8. cout << root->_kv.first << endl;
    9. _InOrder(root->_right);
    10. }
    11. void InOrder()
    12. {
    13. _InOrder(_root);
    14. }
    15. //blackNum记录每条路径黑色结点的数量,refBlackNum为最左路径的黑色结点数量作为基准值
    16. bool Check(Node* cur, int blackNum, int refBlackNum)
    17. {
    18. if (cur == nullptr)
    19. {
    20. if (refBlackNum != blackNum)
    21. {
    22. cout << "黑色节点的数量不相等" << endl;
    23. return false;
    24. }
    25. return true;
    26. }
    27. //检测红黑树性质二
    28. if (cur->_col == RED && cur->_parent->_col == RED)
    29. {
    30. cout << cur->_kv.first << "存在连续的红色节点" << endl;
    31. return false;
    32. }
    33. if (cur->_col == BLACK)
    34. ++blackNum;
    35. return Check(cur->_left, blackNum, refBlackNum)
    36. && Check(cur->_right, blackNum, refBlackNum);
    37. }
    38. bool IsRBTree()
    39. {
    40. //检查根结点是否为黑色
    41. if (_root && _root->_col == RED)
    42. return false;
    43. //检查是否满足红黑树性质二
    44. //思路1:如果当前节点为红色,检测它的孩子是否为红色,但孩子可能为空,需要判断孩子是否为空;
    45. //思路2:如果当前节点为红色,我们去检测它的父亲是否为红色;
    46. //检查是否满足红黑树性质三
    47. //首先每个结点均需要求取从根到当前结点的黑色结点的数量(例如:根到根的黑色结点数量为1)
    48. //将最左路径/最右路径的黑色节点数量作为基准值,求取到下一条路径的空节点时与基准值比较;
    49. int refBlackNum = 0;//基准值
    50. Node* cur = _root;
    51. while (cur)
    52. {
    53. if (cur->_col == BLACK)
    54. refBlackNum++;
    55. cur = cur->_left;
    56. }
    57. return Check(_root, 0, refBlackNum);
    58. }

    欢迎大家批评指正,博主会持续输出优质内容,谢谢各位观众老爷观看,码字画图不易,希望大家给个一键三连支持~ 你的支持是我创作的不竭动力~

  • 相关阅读:
    Mac下通过brew安装多个版本的go并实现切换
    物理层(2.1)
    Git实战技巧-查看不同版本之间的差异和代码的改动
    SD6.23集训总结
    __builtin_return_address()函数的使用方法
    玩转数据可视化之R语言ggplot2:(十一)坐标轴和刻度线设置2
    2022年深圳技能大赛-大数据技术应用职业技能竞赛介绍
    第十八章:Swing自述
    XXE漏洞原理、xxe-lab演示和防御
    vue导航栏滚动下拉条上拉隐藏,下拉显示切换样式变化(源码)
  • 原文地址:https://blog.csdn.net/m0_58963318/article/details/137808900