• 数据结构-红黑树


    目录

    红黑树的概念及结构

    概念

    结构

    红黑树的插入

    红黑树的删除

    判断是否为红黑树

    最长路径

    最短路径


    红黑树的概念及结构

    概念

       红黑树也是一种二叉排序树,在红黑树中每个结点存储着对应的颜色(红色或者黑色),由于AVL树的高度平衡是因为非常频繁地调用旋转来保存自身平衡的,代价较大。所以在AVL树的基础上进一步放宽条件,引入红黑树,即红黑树的最长路径不会比最短路径长两倍。861ef3c37a0342e98eeef6fa481c144a.png一棵红黑树需要满足以下性质才能保证最长路径不会超过最短路径的两倍:

    1,根结点必须是黑色,其他结点不是黑色就是红色

    2,红色结点的双亲结点与孩子结点都是黑色的,也就是不会存在两个相邻的红色结点

    3,对于每一个结点,从该结点到任一叶结点的路径上,每条路径上的黑色结点数量相同

    4,空结点为黑色

    通过上面的性质可以得出一些结论:

    1,最短路径上的结点都是黑色的,而最长路径上的结点一黑一红交替

    2,若一棵红黑树有n个结点则红黑树的高度为h <= 2*log(2) (n+1)

    3,在红黑树中,对于红色结点其出度只能为0或者2

    4,在红黑树中,如果黑色结点只有一个孩子结点,则该孩子结点只能为红色

    结构

       红黑树的链式结构依然用三叉链,红黑树的每一个结点都存储着左孩子结点地址,右孩子结点地址,双亲结点地址,KeyValue数据以及结点的颜色。61f0d7ec94054dbca084b24f3c58505d.png

       新开辟的结点默认选择是红色的:假设新插入的结点默认是黑色的,那么这个结点所在的路径会比其他路径多一个黑色的结点,会破坏性质3,调整起来很麻烦。如果插入的结点默认是红色的,所有路径上的黑色结点数量依然相同,如果插入结点的双亲是黑色的则不需要处理直接结束插入,如果插入结点的双亲是红色的则破坏性质2需要处理。所以选择新插入的结点为红色的代价小。

    红黑树的插入

       红黑树的插入思路与二叉搜索树的插入相同,只不过红黑树需要在插入新结点之后,判断插入之后有没有违反红黑树的性质。如果没有违反则插入成功,否则需要进行修复使之平衡。

    第一步:找到新结点的插入位置,并记录新结点的双亲位置

    b4f3707083fb4c399c66eea789bdc451.png

    第二步:进行插入,如果新结点小于双亲则往左插入,如果新结点大于双亲则往右插入 

    aad613ff33844b7798f9983469add35a.png

    第三步:判断是否需要修复,如果双亲结点的颜色为黑色则不需要修复,如果双亲结点的颜色为红色则需要修复 

    当双亲的颜色为红色时,需要进行修复,而修复的方法分为两种,这两种方法是根据其插入结点的叔叔来决定的。

    1,当叔叔存在并且为红时,进行调色然后继续往上处理------祖父变为红色,双亲与叔叔变为黑色4581c0c9b5d44f47b27a856425291217.pngf69793fe786a478aac127c550a53bf8c.png2,当叔叔不存在或者叔叔的颜色为黑,需要进行旋转与调色处理------(1,双亲是祖父的左孩子并且新插入结点是双亲的左孩子,进行祖父右旋转并将祖父变为红,双亲变为黑。(2,双亲是祖父的左孩子并且新插入结点是双亲的右孩子,进行双亲左旋转祖父右旋转,并将祖父变红,新插入结点变黑。(3,双亲是祖父的右孩子并且新插入结点是双亲的右孩子,进行祖父左旋转并将祖父变为红色,双亲变为黑色。(4,双亲是祖父的右孩子并且新插入结点是双亲的左孩子,进行双亲右旋转祖父左旋转,并将祖父变为红色,新插入结点变为黑色。

    0fd09119b03b430d9affbdca5155d0ad.png6b0e0177fc6f4f8e9a17762fd92cdf02.png

    1. // 旋转代码
    2. //右旋转
    3. void RotateR(Node* root)
    4. {
    5. Node* subL = root->_left;
    6. Node* subLR = subL->_right;
    7. Node* rootParent = root->_parent;
    8. subL->_right = root;
    9. root->_parent = subL;
    10. root->_left = subLR;
    11. if (subLR)
    12. {
    13. subLR->_parent = root;
    14. }
    15. if (rootParent == nullptr)
    16. {
    17. _root = subL;
    18. subL->_parent=nullptr;
    19. }
    20. else
    21. {
    22. if (rootParent->_left == root)
    23. {
    24. rootParent->_left = subL;
    25. }
    26. else if (rootParent->_right == root)
    27. {
    28. rootParent->_right = subL;
    29. }
    30. subL->_parent = rootParent;
    31. }
    32. }
    33. // 左旋转
    34. void RotateL(Node* root)
    35. {
    36. Node* subR = root->_right;
    37. Node* subRL = subR->_left;
    38. Node* rootParent = root->_parent;
    39. subR->_left = root;
    40. root->_parent = subR;
    41. root->_right = subRL;
    42. if (subRL)
    43. {
    44. subRL->_parent = root;
    45. }
    46. if (rootParent == nullptr)
    47. {
    48. _root = subR;
    49. subR->_parent = nullptr;
    50. }
    51. else
    52. {
    53. if (rootParent->_left == root)
    54. {
    55. rootParent->_left = subR;
    56. }
    57. else if (rootParent->_right == root)
    58. {
    59. rootParent->_right = subR;
    60. }
    61. subR->_parent = rootParent;
    62. }
    63. }

    最后将根结点的颜色变为黑色。54c1932186a942eb924e73aed1801a8d.png

    1. pairbool> Insert(const pair& kv)
    2. {
    3. if (_root == nullptr) // 根结点为空,直接插入
    4. {
    5. _root = new Node(kv); // 新结点的地址给_root记录
    6. _root->_parent = nullptr; // 根结点的双亲为空
    7. _root->_colour = BLACK; // 根结点颜色变为黑色
    8. return make_pair(_root, true); // 返回成功插入的结点
    9. }
    10. Node* parent = nullptr; // 记录插入位置的双亲结点
    11. Node* curr = _root; // 寻找插入的位置
    12. while (curr) // curr为空表示找到了插入的位置
    13. {
    14. if (curr->_kv.first > kv.first) // 往左寻找
    15. {
    16. parent = curr;
    17. curr=curr->_left;
    18. }
    19. else if (curr->_kv.first < kv.first) // 往右寻找
    20. {
    21. parent = curr;
    22. curr = curr->_right;
    23. }
    24. else // 已经有对应数据的结点,插入失败
    25. {
    26. return make_pair(curr, false); // 返回已经有对应数据的结点
    27. }
    28. }
    29. Node* newNode = new Node(kv); // 开辟红色新结点,开始插入
    30. if (parent->_kv.first > kv.first) // 比双亲小,往左边插入
    31. {
    32. parent->_left = newNode;
    33. }
    34. else if(parent->_kv.first// 比双亲大,往右边插入
    35. {
    36. parent->_right = newNode;
    37. }
    38. newNode->_parent = parent; // 让新结点的_parent指向双亲
    39. curr = newNode; // curr为新插入的结点,parent为新插入结点的双亲
    40. while (parent && parent->_colour == RED) // 如果新插入结点的双亲为红色则需要调整
    41. {
    42. Node* grandFather = parent->_parent; // 红色结点一定有双亲
    43. if (grandFather->_left == parent) // 叔叔是祖父的右孩子
    44. {
    45. Node* uncle = grandFather->_right;
    46. if (uncle && uncle->_colour == RED) // 叔叔存在而且颜色为红
    47. {
    48. //双亲与叔叔颜色变黑
    49. parent->_colour = uncle->_colour = BLACK;
    50. grandFather->_colour = RED; // 祖父颜色变红
    51. // 迭代
    52. curr = grandFather;
    53. parent = curr->_parent;
    54. }
    55. else // 叔叔不存在或者叔叔颜色为黑
    56. {
    57. if (parent->_left == curr) // 左左---右单选
    58. {
    59. RotateR(grandFather); // 旋转祖父
    60. grandFather->_colour = RED;
    61. parent->_colour = BLACK;
    62. }
    63. else // 左右---左右双旋
    64. {
    65. RotateL(parent); // 先旋转双亲
    66. RotateR(grandFather); // 再旋转祖父
    67. grandFather->_colour = RED; // 祖父变红
    68. curr->_colour = BLACK; // 新插入的结点变黑
    69. }
    70. break;
    71. }
    72. }
    73. else if(grandFather->_right==parent) // 叔叔是祖父的左孩子
    74. {
    75. Node* uncle = grandFather->_left;
    76. if (uncle && uncle->_colour== RED) // 叔叔存在而且颜色为红
    77. {
    78. // 调色加继续向上处理
    79. parent->_colour = uncle->_colour = BLACK;
    80. grandFather->_colour = RED;
    81. // 迭代
    82. curr = grandFather;
    83. parent = curr->_parent;
    84. }
    85. else // 叔叔不存在或者叔叔颜色为黑
    86. {
    87. // 调色加旋转,结束处理
    88. if (parent->_right == curr) // 右右---左单选
    89. {
    90. RotateL(grandFather);
    91. grandFather->_colour = RED;
    92. parent->_colour = BLACK;
    93. }
    94. else // 右左---右左双旋
    95. {
    96. RotateR(parent);
    97. RotateL(grandFather);
    98. grandFather->_colour = RED;
    99. curr->_colour = BLACK;
    100. }
    101. break;
    102. }
    103. }
    104. }
    105. _root->_colour = BLACK; // 将根结点变为黑色
    106. return make_pair(newNode, true); // 插入成功,返回插入结点
    107. }

    红黑树的删除

    第一步:找到要删除的结点

    cda0b1072d9447d3b3faffb6838a73ad.png

    第二步:按照要删除结点的类型进行分类删除:1,左子树为空   2,右子树为空   3,左右子树都为空   4,左右子树都不为空

    第三步:在删除之前,根据删除结点的特征与颜色进行调整

    1,如果删除的结点是红色

    可以直接删除,不会影响红黑树的特性

    2,如果删除的结点是黑色,并且有一个孩子结点(孩子结点一定是红色)

    则将该黑色结点删除,并将该孩子结点变黑交给双亲托管

    3,如果删除的结点是黑色,并且兄弟结点是黑色,其远侄子是红色

    交换双亲与兄弟的颜色,并且将远侄子的颜色变为黑色,双亲进行一次单旋转(删除结点在双亲的左进行左旋转;删除结点在双亲的右进行右旋转);完成之后3特征结束

    a75f8274c0f1418a9ca217b0912c919d.png664f07002d004c9ea61c3b6ae52cd45f.png5b26a6be719540ee9309e065847b0098.png

    4,如果删除结点是黑色,并且兄弟结点是黑色,近侄子是红色,远侄子是黑色

    兄弟与近侄子交换颜色,然后对兄弟做一次单旋转(删除结点在双亲的左进行右旋转;删除结点在双亲的右进行左旋转);完成之后4特征变为3特征继续

    2bce04dc8fbe44f38a9fd94e4dd412f9.pngaf3c11f25c1747f0b9d73f304ca896de.pngdb552dbc9bd34fe9b31d15fedd4b78d8.png

    5,如果删除的结点是黑色,并且兄弟结点的颜色是红色

    交换双亲与兄弟的颜色,然后对双亲做一次单旋转(删除结点在双亲的左进行左旋转;删除结点在双亲的右进行右旋转)这样完成之后5特征变为3特征或者4特征孩子6特征继续

    3ffeb56cb1eb42e89da8731fd7d48def.png

    6,如果删除的结点是黑色,兄弟结点是黑色并且其左右孩子都是黑色(左右孩子可以为空)

    将兄弟结点变为红色,紧接着判断双亲是否为根结点或者是否为红色,如果成立调整结束,如果不成立将child变为parent,parent变为child->_parent继续往上一层调整

    ddad1a7d3fd04bedaaa1ec1936b49764.png

    de1f64a09e5b4db598288b6bb8d07916.png

    要删除的结点只能也只有这样的特征,其他特征都不存在。

    5df36dee868b4371b126eab2ce3b9b73.png

    1. void UpdateB(Node* child, Node* parent)
    2. {
    3. if (child->_colour == RED) // 删除结点为红色
    4. {
    5. return;
    6. }
    7. if (child->_colour==BLACK) // 删除结点为黑色并且有一个孩子
    8. {
    9. if (child->_left && child->_left->_colour==RED)
    10. {
    11. child->_left->_colour = BLACK;
    12. return;
    13. }
    14. else if (child->_right && child->_right->_colour==RED)
    15. {
    16. child->_right->_colour = BLACK;
    17. return;
    18. }
    19. }
    20. while (child!=_root) // 删除结点为黑色没有孩子
    21. {
    22. if (child == parent->_left)
    23. {
    24. Node* brother = parent->_right;
    25. Node* nephewL = brother->_left;
    26. Node* nephewR = brother->_right;
    27. if (brother->_colour == BLACK && nephewR && nephewR->_colour == RED)
    28. {
    29. // 父亲与兄弟结点的颜色互换
    30. COLOUR tmp = brother->_colour;
    31. brother->_colour = parent->_colour;
    32. parent->_colour = tmp;
    33. nephewR->_colour = BLACK; // 兄弟的右孩子换成黑色
    34. RotateL(parent); // 父亲左旋转
    35. return;
    36. }
    37. else if (brother->_colour == BLACK && nephewL && nephewL->_colour == RED)
    38. {
    39. // 兄弟与兄弟左孩子结点的颜色互换
    40. COLOUR tmp = brother->_colour;
    41. brother->_colour = nephewL->_colour;
    42. nephewL->_colour = tmp;
    43. // 兄弟右旋转
    44. RotateR(brother);
    45. }
    46. else if (brother->_colour == BLACK
    47. && (nephewL == nullptr || nephewL->_colour == BLACK)
    48. && (nephewR == nullptr || nephewR->_colour == BLACK))
    49. {
    50. brother->_colour = RED;
    51. if (parent == _root || parent->_colour == RED)
    52. {
    53. parent->_colour = BLACK;
    54. return;
    55. }
    56. //向上调整
    57. child = parent;
    58. parent = child->_parent;
    59. }
    60. else if (brother->_colour == RED)
    61. {
    62. // 父亲与兄弟结点的颜色互换
    63. COLOUR tmp = brother->_colour;
    64. brother->_colour = parent->_colour;
    65. parent->_colour = tmp;
    66. RotateL(parent);
    67. }
    68. }
    69. else if(child==parent->_right)
    70. {
    71. Node* brother = parent->_left;
    72. Node* nephewL = brother->_left;
    73. Node* nephewR = brother->_right;
    74. if (brother->_colour == BLACK && nephewL && nephewL->_colour == RED)
    75. {
    76. // 父亲与兄弟结点的颜色互换
    77. COLOUR tmp = brother->_colour;
    78. brother->_colour = parent->_colour;
    79. parent->_colour = tmp;
    80. nephewL->_colour = BLACK; // 兄弟的左孩子换成黑色
    81. RotateR(parent); // 父亲右旋转
    82. return;
    83. }
    84. else if (brother->_colour == BLACK && nephewR && nephewR->_colour == RED)
    85. {
    86. // 兄弟与兄弟右孩子结点的颜色互换
    87. COLOUR tmp = brother->_colour;
    88. brother->_colour = nephewR->_colour;
    89. nephewR->_colour = tmp;
    90. // 兄弟左旋转
    91. RotateL(brother);
    92. }
    93. else if (brother->_colour == BLACK
    94. && (nephewL == nullptr || nephewL->_colour == BLACK)
    95. && (nephewR == nullptr || nephewR->_colour == BLACK))
    96. {
    97. brother->_colour = RED;
    98. if (parent == _root || parent->_colour == RED)
    99. {
    100. parent->_colour = BLACK;
    101. return;
    102. }
    103. //向上调整
    104. child = parent;
    105. parent = child->_parent;
    106. }
    107. else if (brother->_colour == RED)
    108. {
    109. // 父亲与兄弟结点的颜色互换
    110. COLOUR tmp = brother->_colour;
    111. brother->_colour = parent->_colour;
    112. parent->_colour = tmp;
    113. RotateR(parent);
    114. }
    115. }
    116. }
    117. }
    118. bool Erase(const K& key)
    119. {
    120. if (_root == nullptr) // 红黑树为空不需要删除
    121. {
    122. return false;
    123. }
    124. Node* parent = nullptr;
    125. Node* curr = _root;
    126. while (curr)
    127. {
    128. if (curr->_kv.first > key) // 往左寻找
    129. {
    130. parent = curr;
    131. curr = curr->_left;
    132. }
    133. else if(curr->_kv.first// 往右寻找
    134. {
    135. parent = curr;
    136. curr = curr->_right;
    137. }
    138. else // 找到了开始删除
    139. {
    140. if (curr->_left == nullptr) // 删除结点的左孩子为空
    141. {
    142. if (curr == _root) // 删除的是根结点
    143. {
    144. _root = curr->_right;
    145. if (_root)
    146. {
    147. _root->_parent = nullptr;
    148. _root->_colour = BLACK;
    149. }
    150. }
    151. else // 删除的不是根结点
    152. {
    153. UpdateB(curr, parent); // 删除之前进行调整
    154. if (parent->_left == curr)
    155. {
    156. parent->_left = curr->_right;
    157. }
    158. else
    159. {
    160. parent->_right = curr->_right;
    161. }
    162. if (curr->_right)
    163. {
    164. curr->_right->_parent = parent;
    165. }
    166. }
    167. delete curr;
    168. return true;
    169. }
    170. else if (curr->_right == nullptr) // 删除结点的右孩子为空
    171. {
    172. if (curr == _root) // 删除的是根结点
    173. {
    174. _root = curr->_left;
    175. if (_root)
    176. {
    177. _root->_parent = nullptr;
    178. _root->_colour = BLACK;
    179. }
    180. }
    181. else // 删除的不是根结点
    182. {
    183. UpdateB(curr, parent); // 删除之前进行判断调整
    184. if (parent->_left == curr)
    185. {
    186. parent->_left = curr->_left;
    187. }
    188. else
    189. {
    190. parent->_right = curr->_left;
    191. }
    192. if (curr->_left)
    193. {
    194. curr->_left->_parent = parent;
    195. }
    196. }
    197. delete curr;
    198. return true;
    199. }
    200. else // 删除结点的左右孩子都不空,进行替换删除
    201. {
    202. Node* min = curr->_right;
    203. while (min->_left)
    204. {
    205. min = min->_left;
    206. }
    207. pair tmp = min->_kv;
    208. Erase(tmp.first);
    209. curr->_kv = tmp;
    210. return true;
    211. }
    212. }
    213. }
    214. return false;
    215. }

    判断是否为红黑树

       判断一棵树是否为红黑树只需要验证这颗树是否满足红黑树的性质

    07005b5157be4513ac1fd1e1eaf2e238.png

    1. bool IsRedBlackTree()
    2. {
    3. Node* pRoot = _root;
    4. // 空树也是红黑树
    5. if (nullptr == pRoot)
    6. return true;
    7. // 检测根节点是否满足情况
    8. if (BLACK != pRoot->_colour)
    9. {
    10. return false;
    11. }
    12. int blackcount = 0; // 先计算一条路径上的黑色数量为基准
    13. Node* curr = _root;
    14. while (curr)
    15. {
    16. if (curr->_colour==BLACK)
    17. {
    18. blackcount++;
    19. }
    20. curr = curr->_left;
    21. }
    22. return _IsRedBlackTree(_root, 0, blackcount);
    23. }

    最长路径

    1. int _maxHeight(Node* root)
    2. {
    3. if (root == nullptr)
    4. return 0;
    5. int lh = _maxHeight(root->_left);
    6. int rh = _maxHeight(root->_right);
    7. return lh > rh ? lh + 1 : rh + 1;
    8. }

    最短路径

    1. int _minHeight(Node* root)
    2. {
    3. if (root == nullptr)
    4. return 0;
    5. int lh = _minHeight(root->_left);
    6. int rh = _minHeight(root->_right);
    7. return lh < rh ? lh + 1 : rh + 1;
    8. }

     

  • 相关阅读:
    想裁剪视频时长,用电脑怎么裁剪视频时长
    docker 常用命令整理
    C#学习记录——网络编程基础
    【C++从0到王者】第二十八站:二叉搜索树的应用
    【Java项目】如何设计一个用户签到系统?并且这个签到系统支持7天,14天等不同天数的连续签到功能?
    Matlab optimtool优化阵列天线的幅相激励
    强化学习环境 - robogym - 学习 - 1
    记一次由于google新版本限制升级导致的跨域问题
    springBoot的转发和重定向
    【Elasticsearch<一>✈️✈️】简单安装使用以及各种踩坑
  • 原文地址:https://blog.csdn.net/m0_61433144/article/details/126401073