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

目录

前言

 一、左旋和右旋

左旋(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. 将当前节点设为新的父节点的左子节点。

//左旋(以x为旋转点,向左旋转)
void left_rotate(rbtree* T, Node* x) {Node* y = x->right;//标记到右子节点x->right = y->left;//y的左子节点代替x的右子节点if (x->right != T->nil)x->right->par = x;//如果不为空(nil)其父节点指向xy->par = x->par;//把y的父节点指向x的父节点,此时x与y没有直接联系了if (x->par == T->nil) {//判断x的父节点是否为根结点T->root = y;//如果是的话,y就变为根结点}else {//y顶替x的位置if (x == x->par->left)x->par->left = y;//如果x是父节点的左边,那y就代替x成为左子节点elsex->par->right = y;//如果x是父节点的右边,那y就代替x成为右子节点}//y的左子节点指向x,x的父节点指向yy->left = x;x->par = y;
}

右旋(Right Rotation) 

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

操作如下:

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

//右旋(以x为旋转点,向右旋转)
void right_rotate(rbtree* T, Node* x) {Node* y = x->left;//标记到左子节点yx->left = y->right;//y的右子节点代替x的左子节点if (x->left != T->nil)x->left->par = x;y->par = x->par;//y的父节点指向x的父节点if (x->par == T->nil)T->root = y;//如果x是根结点的话,那么y代替x成为根结点else {if (x == x->par->left)x->par->left = y;elsex->par->right = y;}//y的右子节点指向x,x的父节点为yy->right = x;x->par = y;
}

 二、红黑树的查找

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

//根据key查找
Node* Search_key(rbtree* T, int target) {assert(T);assert(T->root);Node* cur = T->root;while (cur) {if (cur->key == target)return cur;//找到就返回else if (cur->key > target)cur = cur->left;elsecur = cur->right;}printf("The target is not exist\n");return NULL;
}

三、红黑树的删除

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

  • 删除的是叶子节点(下面又分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节点就转化为上面三大类的删除情况(递归即可)。

四、完整代码

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>//宏定义颜色
#define red 0
#define black 1//数据类型Datatype
typedef char Datatype;
//红黑树节点存储结构
typedef struct node {Datatype data;int color;int key;struct node* par;//父节点指针struct node* left, * right;//左右子节点指针
}Node;//红黑树的定义rbtree
typedef struct tree {Node* root;//指向根节点指针Node* nil;//叶子节点(哨兵)
}rbtree;//创建初始化红黑树
rbtree* Create_inittree() {rbtree* T = (rbtree*)malloc(sizeof(rbtree));assert(T);T->nil = (Node*)malloc(sizeof(Node));assert(T->nil);//T->nil是不储存数据的节点,作为空节点代替NULL,也就是哨兵节点(表示空)T->nil->color = black;T->nil->par = NULL;T->nil->left = T->nil->right = NULL;T->root = T->nil;return T;
}//创建一个节点
Node* Create_node(rbtree*T ,Datatype data, int key) {Node* new_node = (Node*)malloc(sizeof(Node));assert(new_node);new_node->data = data;new_node->color = red;//初始化颜色红色//左右父节点为nil哨兵节点new_node->left=new_node->right = T->nil;new_node->par = T->nil;new_node->key = key;return new_node;
}//左旋(以x为旋转点,向左旋转)
void left_rotate(rbtree* T, Node* x) {Node* y = x->right;//标记到右子节点x->right = y->left;//y的左子节点代替x的右子节点if (x->right != T->nil)x->right->par = x;//如果不为空(nil)其父节点指向xy->par = x->par;//把y的父节点指向x的父节点,此时x与y没有直接联系了if (x->par == T->nil) {//判断x的父节点是否为根结点T->root = y;//如果是的话,y就变为根结点}else {//y顶替x的位置if (x == x->par->left)x->par->left = y;//如果x是父节点的左边,那y就代替x成为左子节点elsex->par->right = y;//如果x是父节点的右边,那y就代替x成为右子节点}//y的左子节点指向x,x的父节点指向yy->left = x;x->par = y;
}
//右旋(以x为旋转点,向右旋转)
void right_rotate(rbtree* T, Node* x) {Node* y = x->left;//标记到左子节点yx->left = y->right;//y的右子节点代替x的左子节点if (x->left != T->nil)x->left->par = x;y->par = x->par;//y的父节点指向x的父节点if (x->par == T->nil)T->root = y;//如果x是根结点的话,那么y代替x成为根结点else {if (x == x->par->left)x->par->left = y;elsex->par->right = y;}//y的右子节点指向x,x的父节点为yy->right = x;x->par = y;
}//根据key查找
Node* Search_key(rbtree* T, int target) {assert(T);assert(T->root);Node* cur = T->root;while (cur) {if (cur->key == target)return cur;//找到就返回else if (cur->key > target)cur = cur->left;elsecur = cur->right;}printf("The target is not exist\n");return NULL;
}//删除黑色叶子节点调整
void Del_b_adjust(rbtree* T, Node* x) {//被删除节点x父节点的左边if (x == x->par->left) {Node* p = x->par;//父节点Node* b = p->right;//兄弟节点p->left = T->nil;//删除节点xfree(x);x = NULL;//1.兄弟节点为黑色if (b->color == black) {//1-1没有侄子节点if (b->left == T->nil && b->right == T->nil) {p->color = black;b->color = red;}//1-2左侄节点红色else if (b->left->color == red && b->right == T->nil) {right_rotate(T, b);b->par->color = p->color;p->color = black;left_rotate(T, p);}//1-3右侄子节点红色else if (b->left == T->nil && b->right->color == red) {b->color = p->color;p->color = black;left_rotate(T, p);}//1-4 两个侄子节都是红色else {left_rotate(T, p);b->color = p->color;b->right->color = black;p->color = black;}}//2.兄弟节点为红色else {left_rotate(T, p);b->color = black;p->right->color = red;}}//被删除节点在父节点的右边else {Node* p = x->par;Node* b = p->left;p->right = T->nil;free(x);x = NULL;//1.兄弟节点黑色if (b->color == black) {//1-1没有侄子节点if (b->left == T->nil && b->right == T->nil) {p->color = black;b->color = red;}//1-2兄弟有右子节点else if (b->right->color == red && b->left == T->nil) {left_rotate(T, b);b->par->color = p->color;p->color = black;right_rotate(T, p);}//1-3 兄弟有左子节点else if (b->left->color == red && b->right == T->nil) {b->color = p->color;p->color = black;b->left->color = black;right_rotate(T, p);}//1-4 兄弟有左右子节点else {right_rotate(T, p);b->color = p->color;p->color = black;b->left->color = black;}}//2.兄弟节点为红色else {right_rotate(T, p);b->color = black;p->left->color = red;}}
}
//查找删除替身节点(找后驱)
Node* node_successor(rbtree* T, Node* root) {while (root->left != T->nil)root = root->left;return root;
}
//删除节点操作
void Delete_node(rbtree* T, Node* target) {//1.删除的节点是叶子节点if (target->left == T->nil && target->right == T->nil) {//1-01如果这个节点是红色节点if (target->color == red) {if (target == target->par->left)target->par->left = T->nil;elsetarget->par->right = T->nil;free(target);target = NULL;}//1-02 如果是黑色叶子节点进入到调整elseDel_b_adjust(T, target);}//2.删除的只有一个左孩子的节点else if (target->left != T->nil && target->right == T->nil) {Node* lc = target->left;target->data = lc->data;target->key = lc->key;target->left = T->nil;free(lc);lc = NULL;}//3.删除的只有一个右孩子的节点else if (target->left == T->nil && target->right != T->nil) {Node* rc = target->right;target->data = rc->data;target->key = rc->key;target->right = T->nil;free(rc);rc = NULL;}//4.删除的节点有左右孩子else {Node* sub = node_successor(T, target->right);//找到替代者target->data = sub->data;target->key = sub->key;Delete_node(T, sub);//递归进入到前三种删除方式}T->root->color = black;//根结点为黑色
}

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

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

分享一张壁纸:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/162306.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

创新与重塑,佛塑科技打造集团型 CRM 建设标杆

“十四五”时期是我国全面建成小康社会、实现第一个百年奋斗目标之后&#xff0c;乘势而上开启全面建设社会主义现代化国家新征程、向第二个百年奋斗目标进军的第一个五年。 在政府有序推进“十四五”规划的进程中&#xff0c;佛山佛塑科技集团股份有限公司&#xff08;证券简…

uni-app--》基于小程序开发的电商平台项目实战(七)完结篇

&#x1f3cd;️作者简介&#xff1a;大家好&#xff0c;我是亦世凡华、渴望知识储备自己的一名在校大学生 &#x1f6f5;个人主页&#xff1a;亦世凡华、 &#x1f6fa;系列专栏&#xff1a;uni-app &#x1f6b2;座右铭&#xff1a;人生亦可燃烧&#xff0c;亦可腐败&#xf…

LeetCode【17】电话号码的字母组合

题目&#xff1a; 思路&#xff1a; 参考&#xff1a;https://blog.csdn.net/weixin_46429290/article/details/121888154 和上一个题《子集》的思路一样&#xff0c;先画出树结构&#xff0c;看树的深度&#xff08;遍历层级&#xff09;&#xff0c;树的宽度&#xff08;横向…

【监督学习】基于合取子句进化算法(CCEA)和析取范式进化算法(DNFEA)解决分类问题(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

AI机器视觉多场景应用迸发检测活力,引领食品及包装行业新发展

随着食品安全意识的广泛传播&#xff0c;人们对食品质量和安全的要求越来越高&#xff0c;众多食品包装厂商加速产线数智化转型&#xff0c;迫切需要高效、准确且智能化的检测技术。 在现代食品及包装行业的自动化生产中&#xff0c;涉及到各种各样的识别、检测、测量等环节&a…

用友GRP-U8 SQL注入漏洞复现

0x01 产品简介 用友GRP-U8R10行政事业财务管理软件是用友公司专注于国家电子政务事业&#xff0c;基于云计算技术所推出的新一代产品&#xff0c;是我国行政事业财务领域最专业的政府财务管理软件。 0x02 漏洞概述 用友GRP-U8的bx_historyDataCheck jsp、slbmbygr.jsp等接口存…

C++基础——内存分区模型

1 概述 C程序在执行是&#xff0c;将内存大致分为4个区域&#xff1a; 代码区&#xff1a;用于存放二进制代码&#xff0c;由操作系统进行管理全局区&#xff1a;存放全局变量和静态变量及常量栈区&#xff1a;由编译器自动分配释放&#xff0c;存放函数的参数、局部变量等堆…

React中的key有什么作用

一、是什么 首先&#xff0c;先给出react组件中进行列表渲染的一个示例&#xff1a; const data [{ id: 0, name: abc },{ id: 1, name: def },{ id: 2, name: ghi },{ id: 3, name: jkl } ];const ListItem (props) > {return <li>{props.name}</li>; };co…

Python中的循环语句Cycle学习

二、循环语句 1、什么是循环语句 一般编程语言都有循环语句,为什么呢? 那就问一下自己,我们弄程序是为了干什么? 那肯定是为了方便我们工作,优化我们的工作效率啊。 而计算机和人类不同,计算机不怕苦也不怕累,也不需要休息,可以一直做。 你要知道,计算机最擅长就…

FPR3346501R1012 数据科学与人工智能:主要区别

FPR3346501R1012 数据科学与人工智能:主要区别 当谈到数据科学和人工智能(人工智能)&#xff0c;你会经常发现两个技能路径之间有很多交集。人工智能有许多子集&#xff0c;比如机器学习和深度学习&#xff0c;以及数据科学利用这些技术来解释和分析数据&#xff0c;发现模式…

云上攻防-云原生篇KubernetesK8s安全APIKubelet未授权访问容器执行

文章目录 K8S集群架构解释K8S集群攻击点-重点API Server未授权访问&kubelet未授权访问复现k8s集群环境搭建1、攻击8080端口&#xff1a;API Server未授权访问2、攻击6443端口&#xff1a;API Server未授权访问3、攻击10250端口&#xff1a;kubelet未授权访问 K8S集群架构解…

让GPT回复图片的咒语

咒语如下&#xff1a; 帮我画一张图关于XXXXX,用3/8Markdown 写&#xff0c;不要有反斜钱,不要用代码块。使用Unsplash APl(https://source.unsplash.com/1280x720/?<PUT YOUR QUERY HERE >) Over! ​​​​​​​

Android---DVM以及ART对JVM进行优化

Dalvik Dalvik 是 Google 公司自己设计用于 Android 平台的 Java 虚拟机&#xff0c;Android 工程师编写的 Java 或者 Kotlin 代码最终都是在这台虚拟机中被执行的。在 Android 5.0 之前叫作 DVM&#xff0c;5.0 之后改为 ART&#xff08;Android Runtime&#xff09;。在整个…

GPIO基本原理

名词解释 高低电平&#xff1a;GPIO引脚电平范围&#xff1a;0V~3.3V&#xff08;部分引脚可容忍5V&#xff09;数据0就是0V&#xff0c;代表低电平&#xff1b;数据1就是3.3V&#xff0c;代表高电平&#xff1b; STM32是32位的单片机&#xff0c;所以内部寄存器也都是32位的…

国产单片机PY32F002B,32位ARM架构Cortex -M0+内核,性价比高

PY32F002B是普冉推出的新一代入门级32位MCU&#xff0c;内核使用 ARM Cortex M0&#xff0c;主频最高支持到24M&#xff0c;24K FLASH 3K SRAM存储&#xff0c;并支持1.7V~5.5V宽工作电压&#xff0c;-40 ~ 85 C工作温度。拥有1 x 12 位ADC、I2C、SPI、USART、TIM、LPTIM、IWDT…

麻了,别再为难软件测试员了

前言 有不少技术友在测试群里讨论&#xff0c;近期的面试越来越难了&#xff0c;要背的八股文越来越多了,考察得越来越细&#xff0c;越来越底层&#xff0c;明摆着就是想让我们徒手造航母嘛&#xff01;实在是太为难我们这些测试工程师了。 这不&#xff0c;为了帮大家节约时…

C# OpenCvSharp 利用Lab空间把春天的场景改为秋天

效果 项目 代码 using OpenCvSharp; using System; using System.Diagnostics; using System.Drawing; using System.Drawing.Imaging; using System.Windows.Forms;namespace OpenCvSharp_Demo {public partial class Form1 : Form{public Form1(){InitializeComponent();}st…

LabVIEW生产者消费者架构

LabVIEW生产者消费者架构 生产者/消费者模式可以轻松地同时处理多个进程&#xff0c;同时还能以不同速率迭代。 缓冲通信 当多个进程以不同速度运行时&#xff0c;就适合采用进程间缓冲通信。有了足够大的缓冲区后&#xff0c;生产者循环可以以快于消费者循环的速度运行&…

Ubuntu 18.04 LTS中cmake-gui编译opencv-3.4.16并供Qt Creator调用

一、安装opencv 1.下载opencv-3.4.16的源码并解压 2.在解压后的文件夹内新建文件夹build以及opencv_install 3.启动cmake-gui并设置 sudo cmake-gui&#xff08;1&#xff09;设置界面中source及build路径 &#xff08;2&#xff09;点击configure&#xff0c;选择第一个def…

实现实时美颜:主播直播美颜SDK的技术细节

在今天的数字时代&#xff0c;直播和实时互动成为了日常生活的一部分&#xff0c;而主播直播美颜SDK的出现为用户提供了更加精美的视觉体验。这项技术的背后有着复杂的技术细节&#xff0c;从图像处理到机器学习&#xff0c;本文将深入探讨主播直播美颜SDK的技术细节&#xff0…