• 数据结构-----红黑树的删除操作


    目录

    前言

     一、左旋和右旋

    左旋(Left Rotation)

    右旋(Right Rotation) 

     二、红黑树的查找

    三、红黑树的删除

    1.删除的是叶子节点

    1.1删除节点颜色为红色

    1.2删除节点颜色为黑色

    1.2-1 要删除节点D为黑色,兄弟节点没有左右孩子

    1.2-2 要删除节点D为黑色,兄弟节点有左孩子,右孩子为空

    1.2-3 要删除节点D为黑色,兄弟节点有右孩子,左孩子为空

    1.2-4 要删除节点为黑色,兄弟节点左右孩子都存在,且为红色

     1.2-5 要删除节点为黑色,兄弟节点为红色

    2.删除节点只有左孩子,没有右孩子

    3.删除节点只有右孩子,没有左孩子 

     4.删除节点有左右子节点,且都为红色

    四、完整代码


    前言

             只有流过血的手指,才能弹出世间的绝唱。 —— 泰戈尔

            今天我们接着学习红黑树,前面学习了红黑树的插入操作,那这次就学习红黑树的删除操作,相较于红黑树的插入操作,红黑树的删除操作更加复杂,但是别急,下面我会非常详细的去讨论这个过程,以及提供完整的代码,好了,开始上正文。

    相关链接

     红黑树介绍:数据结构-----红黑树简介_Gretel Tade的博客-CSDN博客

    红黑树的插入:数据结构-----红黑树的插入_Gretel Tade的博客-CSDN博客

    再讲之前,我分享一个网址给大家(链接:Red/Black Tree Visualization),这个是一个红黑树模拟器的网址,你们可以去进行红黑树插入删除遍历等操作,可以自己试试看。如下图所示:

     一、左旋和右旋

    左旋(Left Rotation)

    左旋是一种将某个节点的右子节点旋转上来的操作。也就是说当前节点的右子节点顶替了自己,然后自己变为右子节点的左子节点,以保持树的平衡。

    操作如下:

    1. 将当前节点的右子节点设为新的父节点。
    2. 将新的父节点的左子节点设为当前节点的右子节点。
    3. 如果当前节点有父节点,将新的父节点替代当前节点的位置。
    4. 将当前节点设为新的父节点的左子节点。

    1. //左旋(以x为旋转点,向左旋转)
    2. void left_rotate(rbtree* T, Node* x) {
    3. Node* y = x->right;//标记到右子节点
    4. x->right = y->left;//y的左子节点代替x的右子节点
    5. if (x->right != T->nil)
    6. x->right->par = x;//如果不为空(nil)其父节点指向x
    7. y->par = x->par;//把y的父节点指向x的父节点,此时x与y没有直接联系了
    8. if (x->par == T->nil) {//判断x的父节点是否为根结点
    9. T->root = y;//如果是的话,y就变为根结点
    10. }
    11. else {
    12. //y顶替x的位置
    13. if (x == x->par->left)
    14. x->par->left = y;//如果x是父节点的左边,那y就代替x成为左子节点
    15. else
    16. x->par->right = y;//如果x是父节点的右边,那y就代替x成为右子节点
    17. }
    18. //y的左子节点指向x,x的父节点指向y
    19. y->left = x;
    20. x->par = y;
    21. }

    右旋(Right Rotation) 

    同样的右旋也是将左子节点顶替自己成为父节点, 然后自己成为左子节点的右子节点。

    操作如下:

    1. 将当前节点的左子节点设为新的父节点
    2. 将新的父节点的右子节点设为当前节点的左子节点
    3. 如果当前节点有父节点,将新的父节点替代当前节点的位置
    4. 将当前节点设为新的父节点的右子节点

    1. //右旋(以x为旋转点,向右旋转)
    2. void right_rotate(rbtree* T, Node* x) {
    3. Node* y = x->left;//标记到左子节点y
    4. x->left = y->right;//y的右子节点代替x的左子节点
    5. if (x->left != T->nil)
    6. x->left->par = x;
    7. y->par = x->par;//y的父节点指向x的父节点
    8. if (x->par == T->nil)
    9. T->root = y;//如果x是根结点的话,那么y代替x成为根结点
    10. else {
    11. if (x == x->par->left)
    12. x->par->left = y;
    13. else
    14. x->par->right = y;
    15. }
    16. //y的右子节点指向x,x的父节点为y
    17. y->right = x;
    18. x->par = y;
    19. }

     二、红黑树的查找

    红黑树是二叉排序树,查找也跟AVL树是一样的,根据key的值的大小去向左向右查找,找到就返回即可。

    1. //根据key查找
    2. Node* Search_key(rbtree* T, int target) {
    3. assert(T);
    4. assert(T->root);
    5. Node* cur = T->root;
    6. while (cur) {
    7. if (cur->key == target)
    8. return cur;//找到就返回
    9. else if (cur->key > target)
    10. cur = cur->left;
    11. else
    12. cur = cur->right;
    13. }
    14. printf("The target is not exist\n");
    15. return NULL;
    16. }

    三、红黑树的删除

    红黑树的删除所有情况如下所示:

    • 删除的是叶子节点(下面又分2种情况)
      • 删除节点的颜色是红色
      • 删除节点的颜色是黑色(下面再分5种情况)
        1. 兄弟节点没有左右孩子
        2. 兄弟节点左孩子为红色,右孩子为黑色
        3. 兄弟节点右孩子为红色,左孩子为黑色
        4. 兄弟节点有左右孩子,且都为红色
        5. 兄弟节点有左右孩子,且都为黑色(兄弟节点为红色)
    • 删除的只有左子节点,没有右子节点
    • 删除的只有右子节点,没有左子节点
    • 删除的既有左子节点,又有右子节点

     以上就是红黑树删除操作的全部情况,非常清晰,那这里就要去进行一个一个来讨论了。

    以下图片标注说明

    D:表示要删除的节点

    P:表示删除节点的父节点

    B:表示D的兄弟节点

    LN:表示B的左子节点

    RN:表示B的右子节点

    1.删除的是叶子节点

    如果删除的是叶子节点,那就要去看删除节点的颜色来操作,以下分两种情况:

    • 删除节点颜色为红色
    • 删除节点颜色为黑色

     注意事项

    删除的是叶子结点,右两种可能,也就是要删除的叶子结点是左叶子结点或者是右叶子结点,下面我会去通过删除左叶子结点来去讨论上面这些过程,如果要删除右叶子结点,这里只需要进行对称操作就行了

    1.1删除节点颜色为红色

     直接删除,因为删除掉红色节点不会影响到红黑树的基本特性

    1.2删除节点颜色为黑色

    如果要删除节点的颜色为黑色的话,那么这里就要考虑到被删除节点的兄弟节点的颜色了:

    • 如果兄弟节点颜色为黑色,那么父节点颜色既可以是黑色也可以是红色(下图用白色表示)
    • 如果兄弟节点颜色为红色,那么父节点颜色只能是黑色
    1.2-1 要删除节点D为黑色,兄弟节点没有左右孩子

    操作如下:

    • 删除D节点
    • P的颜色变为黑色
    • B的颜色变为红色

    1.2-2 要删除节点D为黑色,兄弟节点有左孩子,右孩子为空

    操作如下: 

    •  删除D节点
    • 对B进行右旋
    • LN的颜色变为P的颜色
    • P的颜色变为黑色
    • 对P进行左旋

    1.2-3 要删除节点D为黑色,兄弟节点有右孩子,左孩子为空

    操作如下:

    •  删除D节点
    • B的颜色变P的颜色
    • P的颜色变为黑色
    • 对P进行左旋

    1.2-4 要删除节点为黑色,兄弟节点左右孩子都存在,且为红色

    操作如下:

    •  删除D节点
    • 对P进行左旋
    • B的颜色变为P的颜色
    • P的颜色染为黑色
    • RN的颜色染为黑色

     1.2-5 要删除节点为黑色,兄弟节点为红色

    对于这种情况的话,父节点P的颜色那就是必须为黑色了,操作如下:

    • 删除节点D
    • 对P进行左旋
    • B的颜色染黑
    • LN的颜色染红

    这里只讨论了删除节点作为左叶子节点的情况,还有作为右叶子结点的情况还没有说,但是操作跟上面这5种是一模一样的,只是个对称而已,这里就不多说了,各位可以自己照着上面的方式进行画图理解

    2.删除节点只有左孩子,没有右孩子

    对于这种情况,也就只有下图这种样式:

    • 将D的值替换为LC的值
    • 删除LC节点 

    3.删除节点只有右孩子,没有左孩子 

     对于这种情况,也是只有下图的样式:

    • 将D的值替换为RC的值
    • 删除RC节点

     4.删除节点有左右子节点,且都为红色

     对于这种情况处理,我们在前面学习二叉排序树的时候就已经知道了,首先要找到这个节点的后驱来替代这个节点,也就是在这个节点右子树找到最小的那个节点temp,替代这个被删除的节点D,然后问题就转换为删除temp节点,对于t删除emp节点就转化为上面三大类的删除情况(递归即可)。

    四、完整代码

    1. #include
    2. #include
    3. #include
    4. #include
    5. //宏定义颜色
    6. #define red 0
    7. #define black 1
    8. //数据类型Datatype
    9. typedef char Datatype;
    10. //红黑树节点存储结构
    11. typedef struct node {
    12. Datatype data;
    13. int color;
    14. int key;
    15. struct node* par;//父节点指针
    16. struct node* left, * right;//左右子节点指针
    17. }Node;
    18. //红黑树的定义rbtree
    19. typedef struct tree {
    20. Node* root;//指向根节点指针
    21. Node* nil;//叶子节点(哨兵)
    22. }rbtree;
    23. //创建初始化红黑树
    24. rbtree* Create_inittree() {
    25. rbtree* T = (rbtree*)malloc(sizeof(rbtree));
    26. assert(T);
    27. T->nil = (Node*)malloc(sizeof(Node));
    28. assert(T->nil);
    29. //T->nil是不储存数据的节点,作为空节点代替NULL,也就是哨兵节点(表示空)
    30. T->nil->color = black;
    31. T->nil->par = NULL;
    32. T->nil->left = T->nil->right = NULL;
    33. T->root = T->nil;
    34. return T;
    35. }
    36. //创建一个节点
    37. Node* Create_node(rbtree*T ,Datatype data, int key) {
    38. Node* new_node = (Node*)malloc(sizeof(Node));
    39. assert(new_node);
    40. new_node->data = data;
    41. new_node->color = red;//初始化颜色红色
    42. //左右父节点为nil哨兵节点
    43. new_node->left=new_node->right = T->nil;
    44. new_node->par = T->nil;
    45. new_node->key = key;
    46. return new_node;
    47. }
    48. //左旋(以x为旋转点,向左旋转)
    49. void left_rotate(rbtree* T, Node* x) {
    50. Node* y = x->right;//标记到右子节点
    51. x->right = y->left;//y的左子节点代替x的右子节点
    52. if (x->right != T->nil)
    53. x->right->par = x;//如果不为空(nil)其父节点指向x
    54. y->par = x->par;//把y的父节点指向x的父节点,此时x与y没有直接联系了
    55. if (x->par == T->nil) {//判断x的父节点是否为根结点
    56. T->root = y;//如果是的话,y就变为根结点
    57. }
    58. else {
    59. //y顶替x的位置
    60. if (x == x->par->left)
    61. x->par->left = y;//如果x是父节点的左边,那y就代替x成为左子节点
    62. else
    63. x->par->right = y;//如果x是父节点的右边,那y就代替x成为右子节点
    64. }
    65. //y的左子节点指向x,x的父节点指向y
    66. y->left = x;
    67. x->par = y;
    68. }
    69. //右旋(以x为旋转点,向右旋转)
    70. void right_rotate(rbtree* T, Node* x) {
    71. Node* y = x->left;//标记到左子节点y
    72. x->left = y->right;//y的右子节点代替x的左子节点
    73. if (x->left != T->nil)
    74. x->left->par = x;
    75. y->par = x->par;//y的父节点指向x的父节点
    76. if (x->par == T->nil)
    77. T->root = y;//如果x是根结点的话,那么y代替x成为根结点
    78. else {
    79. if (x == x->par->left)
    80. x->par->left = y;
    81. else
    82. x->par->right = y;
    83. }
    84. //y的右子节点指向x,x的父节点为y
    85. y->right = x;
    86. x->par = y;
    87. }
    88. //根据key查找
    89. Node* Search_key(rbtree* T, int target) {
    90. assert(T);
    91. assert(T->root);
    92. Node* cur = T->root;
    93. while (cur) {
    94. if (cur->key == target)
    95. return cur;//找到就返回
    96. else if (cur->key > target)
    97. cur = cur->left;
    98. else
    99. cur = cur->right;
    100. }
    101. printf("The target is not exist\n");
    102. return NULL;
    103. }
    104. //删除黑色叶子节点调整
    105. void Del_b_adjust(rbtree* T, Node* x) {
    106. //被删除节点x父节点的左边
    107. if (x == x->par->left) {
    108. Node* p = x->par;//父节点
    109. Node* b = p->right;//兄弟节点
    110. p->left = T->nil;
    111. //删除节点x
    112. free(x);
    113. x = NULL;
    114. //1.兄弟节点为黑色
    115. if (b->color == black) {
    116. //1-1没有侄子节点
    117. if (b->left == T->nil && b->right == T->nil) {
    118. p->color = black;
    119. b->color = red;
    120. }
    121. //1-2左侄节点红色
    122. else if (b->left->color == red && b->right == T->nil) {
    123. right_rotate(T, b);
    124. b->par->color = p->color;
    125. p->color = black;
    126. left_rotate(T, p);
    127. }
    128. //1-3右侄子节点红色
    129. else if (b->left == T->nil && b->right->color == red) {
    130. b->color = p->color;
    131. p->color = black;
    132. left_rotate(T, p);
    133. }
    134. //1-4 两个侄子节都是红色
    135. else {
    136. left_rotate(T, p);
    137. b->color = p->color;
    138. b->right->color = black;
    139. p->color = black;
    140. }
    141. }
    142. //2.兄弟节点为红色
    143. else {
    144. left_rotate(T, p);
    145. b->color = black;
    146. p->right->color = red;
    147. }
    148. }
    149. //被删除节点在父节点的右边
    150. else {
    151. Node* p = x->par;
    152. Node* b = p->left;
    153. p->right = T->nil;
    154. free(x);
    155. x = NULL;
    156. //1.兄弟节点黑色
    157. if (b->color == black) {
    158. //1-1没有侄子节点
    159. if (b->left == T->nil && b->right == T->nil) {
    160. p->color = black;
    161. b->color = red;
    162. }
    163. //1-2兄弟有右子节点
    164. else if (b->right->color == red && b->left == T->nil) {
    165. left_rotate(T, b);
    166. b->par->color = p->color;
    167. p->color = black;
    168. right_rotate(T, p);
    169. }
    170. //1-3 兄弟有左子节点
    171. else if (b->left->color == red && b->right == T->nil) {
    172. b->color = p->color;
    173. p->color = black;
    174. b->left->color = black;
    175. right_rotate(T, p);
    176. }
    177. //1-4 兄弟有左右子节点
    178. else {
    179. right_rotate(T, p);
    180. b->color = p->color;
    181. p->color = black;
    182. b->left->color = black;
    183. }
    184. }
    185. //2.兄弟节点为红色
    186. else {
    187. right_rotate(T, p);
    188. b->color = black;
    189. p->left->color = red;
    190. }
    191. }
    192. }
    193. //查找删除替身节点(找后驱)
    194. Node* node_successor(rbtree* T, Node* root) {
    195. while (root->left != T->nil)
    196. root = root->left;
    197. return root;
    198. }
    199. //删除节点操作
    200. void Delete_node(rbtree* T, Node* target) {
    201. //1.删除的节点是叶子节点
    202. if (target->left == T->nil && target->right == T->nil) {
    203. //1-01如果这个节点是红色节点
    204. if (target->color == red) {
    205. if (target == target->par->left)
    206. target->par->left = T->nil;
    207. else
    208. target->par->right = T->nil;
    209. free(target);
    210. target = NULL;
    211. }
    212. //1-02 如果是黑色叶子节点进入到调整
    213. else
    214. Del_b_adjust(T, target);
    215. }
    216. //2.删除的只有一个左孩子的节点
    217. else if (target->left != T->nil && target->right == T->nil) {
    218. Node* lc = target->left;
    219. target->data = lc->data;
    220. target->key = lc->key;
    221. target->left = T->nil;
    222. free(lc);
    223. lc = NULL;
    224. }
    225. //3.删除的只有一个右孩子的节点
    226. else if (target->left == T->nil && target->right != T->nil) {
    227. Node* rc = target->right;
    228. target->data = rc->data;
    229. target->key = rc->key;
    230. target->right = T->nil;
    231. free(rc);
    232. rc = NULL;
    233. }
    234. //4.删除的节点有左右孩子
    235. else {
    236. Node* sub = node_successor(T, target->right);//找到替代者
    237. target->data = sub->data;
    238. target->key = sub->key;
    239. Delete_node(T, sub);//递归进入到前三种删除方式
    240. }
    241. T->root->color = black;//根结点为黑色
    242. }

    代码很长,相较于红黑树的插入而已红黑树的删除更为复杂,各位看官慢慢看,我把上面这些情况都写得很详细了,相信你们可以理解。学会红黑树的插入和删除就基本上学会了红黑树啦,恭喜你哦!

    好了,以上就是本期的全部内容了,我们下一次见!拜拜! 

    分享一张壁纸:

  • 相关阅读:
    企业知识库构建:关于企业知识库及知识平台搭建的重要性!
    在CentOS编译Git源码
    J9数字论:最近爆火的Web3.0到底是什么?
    java计算机毕业设计基于springboo+vue的人事管理系统
    【Git】 git push 提示Not possible to fast-forward,无法提交也无法提交程序
    数字IC后端实现 |TSMC 12nm 与TSMC 28nm Metal Stack的区别
    js图片url反转file文件 vue
    个人信息保护视域下知情同意框架的应用困境与对策探析
    一种计算整数位1个数的新方法
    拔河攻略要点
  • 原文地址:https://blog.csdn.net/m0_73633088/article/details/133881888