文章目录
- 红黑树删除
-
- 概念
- 步骤
-
- [按照 BST 的删除操作, 保证顺序性和左根右](#按照 BST 的删除操作, 保证顺序性和左根右)
- [判断是否满足颜色属性(根叶黑 不红红 黑路同)](#判断是否满足颜色属性(根叶黑 不红红 黑路同))
-
- [y 红 x 黑](#y 红 x 黑)
- [y 黑 x 红](#y 黑 x 红)
- [y 黑 x 黑](#y 黑 x 黑)
-
- [x 在右边 w 在左边, 根据 w 和 w 孩子的颜色](#x 在右边 w 在左边, 根据 w 和 w 孩子的颜色)
-
- [a :**w 为黑**, w 至少有一个红色节点 RED](#a :w 为黑, w 至少有一个红色节点 RED)
- [b:**w 为黑**, w 没有红孩子, 两个孩子都是黑色](#b:w 为黑, w 没有红孩子, 两个孩子都是黑色)
- 为什么这么做?(物理意义)
-
-
- [c:**w 为红**,w 孩子一定为黑,w 的父亲 p 一定为黑](#c:w 为红,w 孩子一定为黑,w 的父亲 p 一定为黑)
- [x 在左边 w 在右边(镜像对称)](#x 在左边 w 在右边(镜像对称))
-
- [(a)**w 为黑**, w 至少有一个红色节点 RED](#(a)w 为黑, w 至少有一个红色节点 RED)
- [(b)**w 为黑**, w 没有红孩子, 两个孩子都是黑色](#(b)w 为黑, w 没有红孩子, 两个孩子都是黑色)
- [(c)**w 为红**,w 孩子一定为黑,w 的父亲 p 一定为黑](#(c)w 为红,w 孩子一定为黑,w 的父亲 p 一定为黑)
-
- [y 红 x 红:](#y 红 x 红:)
- 代码
红黑树删除
概念
在已经建立好的红黑树里删除一个节点 z.删除完后需要满足红黑树性质(左根右 根叶黑 不红红 黑路同)
步骤
按照 BST 的删除操作, 保证顺序性和左根右
先找到被删除数据 z 所在的结点
分类讨论:
(1)z 的度是 1/0:用 z 唯一的孩子顶替 z 的位置
(2)z 的度是 2: 找到 z 的中序遍历前驱或者后继结点 y(度 1/0(一定没有两个孩子)), z-> data = y-> data, 问题转为删除 y,转化为了情况(1)最终删除的结点一定是一个度为 1/0 度结点, 删除 y 让 y 唯一的一个孩子 x 顶替 y 的位置。
此时满足了左根右, 其余的性质需要考虑
判断是否满足颜色属性(根叶黑 不红红 黑路同)
y 红 x 黑
y 不是根, 满足根叶黑,满足 不红红 ,满足 黑路同(删除的是红色节点 y 也不影响)
满足红黑树性质 ,无需调整

y 黑 x 红
如果 y 是根, 破坏根叶黑;如果 y 的父亲是红色, 也会破坏 不红红 ;黑路同 一定破坏
调整方法:x 变成黑即可

y 黑 x 黑
一定破坏黑路同--> 不好调整, 本质还原回 B 树调整
调整方法分类讨论: 旋转
x 节点:删除后顶替 y 的节点(特别的删除 y 后,x 变为根节点 就无需看删除后 y 的兄弟,直接变黑即可)
y 节点:待删除节点
p 结点:黑/红 删除之前 p 是 y 的父亲,删除 y 之后 p 是 x 的父亲
w 结点:黑/红 删除之前 w 是 y 的兄弟,删除 y 之后 w 是 x 的兄弟
看删除之后的关系,删除 y 之后 x 继承 y 的位置,此时 p 是 x 的父亲,w 是 x 的兄弟
x 在右边 w 在左边, 根据 w 和 w 孩子的颜色
a :w 为黑, w 至少有一个红色节点 RED
w 的父亲 p 可能红色, 可能黑色
1.w 有红孩子 red, red 是 w 左孩子.LL 情况:w 是 p 的左孩子,red 是 w 的左孩子.
调整方法一:w 变成 p 的颜色,对 p 右旋,RED 和 p 变黑。(统一了父节点 p 黑/红的操作)
以 x 为空节点来举例画图便利,x 为非空也同理
p 为黑色

p 为红色

调整方法二: 分类讨论:
- 父节点黑: 对 p 右旋,red 变黑

- 父节点红就只需要对 p 右旋

2.red 是 w 右孩子.LR 情况:w 是 p 的左孩子,red 是 w 的右孩子.
调整:W 左旋,w 变红,red 变黑,交换 red 和 w 指针,转化为了 LL,按照 LL 的情况调整(a.1 情况)

注:如果 w 有两个红色孩子,看成 LL 情况 比看成 LR 少调整。(看成 LR 和 LL 都行)
b:w 为黑, w 没有红孩子, 两个孩子都是黑色
引入概念---双黑结点:删除结点 y 的时候,认为 y 的颜色留下了,x 顶替 y 的位置时 x 就具有两层黑色,称为双黑结点。双黑结点只是逻辑上的一个概念。双黑结点对黑高的贡献是 2
调整:认为 x 是双黑结点,w 和 x 一起褪一层黑色,w 变红色,褪掉的黑色给父亲 p。
p 如果是红色,p 变黑,调整结束。

p 如果本来就是黑的,p 就称为了双黑结点,把 p 看成新的被调整的结点 x,继续循环向上调整、

cw->color = RED; // 第1步:兄弟变红(兄弟子树黑高减1) x = p; // 第2步:双黑结点上移给父亲 p = x->pa; // 第3步:更新父亲指针,为下一轮循环做准备为什么这么做?(物理意义)
- 当前状态 :
x这边欠一层黑(双黑),兄弟w那边不欠(黑高正常)。- 目标:让两边平衡。
- 操作 :
- 把兄弟
w涂红 → 兄弟子树的黑高 减 1(现在它也"欠"一层了)。- 从
x和w各"抠"出一层黑,合成一层正常的黑色,还给父亲p。- 此时父亲
p身上多了一层黑(变双黑),所以把x指向p,继续检查。"向上调整" 本质上就是:兄弟让出一层黑(变红),让双黑上移给父亲;如果父亲扛得住(红变黑)就结束,扛不住(黑变双黑)就继续往上推,直到根结点或遇到红结点为止。 这就像一层一层地"追债",直到把多出来的黑色消掉。
c:w 为红,w 孩子一定为黑,w 的父亲 p 一定为黑
调整:对 p 右旋转,w 变黑,p 变红,新的兄弟w = p-> I,此时相当于把 p 的兄弟变成了黑色,讨论有无红孩子,转化为了情况 (a)/(b)

x 在左边 w 在右边(镜像对称)
(a)w 为黑, w 至少有一个红色节点 RED
w 的父亲 p 可能红色, 可能黑色
a.1)red 是 w 右孩子.RR 情况:w 是 p 的右孩子,red 是 w 的右孩子.
调整方法一:w 变成 p 的颜色,对 p 左旋,RED 和 p 变黑。(统一了父节点 p 黑/红的操作)
调整方法二: 分类讨论:
-
父节点黑: 对 p 左旋,red 变黑
-
父节点红就只需要对 p 左旋
a.2)red 是 w 左孩子.RL 情况:w 是 p 的右孩子,red 是 w 的左孩子.
调整:W 右旋,w 变红,red 变黑,交换 red 和 w 指针,转化为了 LL,按照 RR 的情况调整
注意:如果 w 有 两个红色孩子,看成 RR 情况。(看成 LR 和 LL 都行)
(b)w 为黑, w 没有红孩子, 两个孩子都是黑色
引入概念---双黑结点:删除结点 y 的时候,认为 y 的颜色留下了,x 顶替 y 的位置时 x 就具有两层黑色,称为双黑结点。双黑结
点只是逻辑上的一个概念。双黑结点对黑高的贡献是 2
调整 :认为 x 是双黑结点,w 和 x 一起褪一层黑色,w 变红色,褪掉的黑色给父亲 p。p 如果是红色,p 变黑,调整结束。
p 如果本来就是黑的,p 就称为了双黑结点,把 p 看成新的被调整的结点 x,继续循环向上调整。(看成上面删除了 x 的父亲,会让黑高不变,如果到了根节点就舍弃掉这个黑色)
(c)w 为红,w 孩子一定为黑,w 的父亲 p 一定为黑
调整:对 p 左旋转,w 变黑,p 变红,w = p-> r,转化为了情况 (a)/(b)
y 红 x 红:
不满足不红红,情况不存在
代码
c
#include<stdio.h>
#include<stdlib.h>
#define RED 1
#define BLACK 0
typedef struct rnode{
struct rnode* pa;
struct rnode* l;
struct rnode* r;
int color;
int data;
}RBNode,*RBTree;
//创建节点
RBNode* createNode(RBNode* p,RBNode* l,RBNode* r,int k){
RBNode* z=(RBNode*)malloc(sizeof(RBNode));
//if(z==NULL)一般不会内存分配不了
z->pa=p;
z->l=l;
z->r=r;
z->color=RED;//默认插入为红色
z->data=k;
return z;
}
//x是失衡节点
//!!需要注意:x为根会改变根
RBTree rightRotate(RBTree root,RBNode* x){
RBNode* y=x->l;
RBNode* ry=y->r;
RBNode* fx=x->pa;
if(fx==NULL)root=y;//x为根会改变根
x->l=ry;
x->pa=y;
y->r=x;
y->pa=fx;
if(ry)ry->pa=x;
if(fx)
{
if(fx->l==x)fx->l=y;
else fx->r=y;
}
return root;
}
RBTree leftRotate(RBTree root,RBNode* x){
RBNode* y=x->r;
RBNode* ly=y->l;
RBNode* fx=x->pa;
if(fx==NULL)root=y;//x为根会改变根
x->r=ly;
x->pa=y;
y->l=x;
y->pa=fx;
if(ly)ly->pa=x;
if(fx)
{
if(fx->l==x)fx->l=y;
else fx->r=y;
}
return root;
}
//父亲p为RED 一定有黑色的祖父 分类讨论叔叔u的颜色
//u为BLACK 一次旋转调整
//u为RED z长辈u,g,p变色 循环往上直达根节点 最后把根节点变黑
RBTree fixUp(RBTree root,RBNode* z){
RBNode* p=z->pa;
RBNode* g=p->pa;
RBNode* u=NULL;
//父亲p为红色就开始调整
while(p&&p->color==RED){
if(g->r==p){
u=g->l;//叔叔在左边
if(u&&u->color==RED){
//z长辈u,g,p变色
p->color=BLACK;
g->color=RED;
u->color=BLACK;
//更新p,g
z=g;
p=z->pa;
if(p)g=p->pa;//调整到 z为根节点
}
else//u为黑色(u不存在或者u存在)
{
//旋转调整
if(z==p->r)
{
//RR
root=leftRotate(root,g);
p->color=BLACK;
g->color=RED;
}
else
{
//RL
root=rightRotate(root,p);
root=leftRotate(root,g);
g->color=RED;
z->color=BLACK;
}
break;
}
}else
{
u=g->r;//叔叔在右边
if(u&&u->color==RED){
//z长辈u,g,p变色
p->color=BLACK;
g->color=RED;
u->color=BLACK;
//更新p,g
z=g;
p=z->pa;
if(p)g=p->pa;
}
else//u为黑色(u不存在或者u存在)
{
//旋转调整(同理,镜面对称)
if(z==p->r)
{
//LR
root=leftRotate(root,p);
root=rightRotate(root,g);
g->color=RED;
z->color=BLACK;
}
else
{
//LL
root=rightRotate(root,g);
p->color=BLACK;
g->color=RED;
}
break;
}
}
}
//不断把长辈变红过程中到了根节点
if(z->pa==NULL)z->color=BLACK;
return root;
}
//插入 插入节点z为红色
RBTree insert(RBTree root,int k){
if(root==NULL){
//空树
root=createNode(NULL,NULL,NULL,k);
root->color=BLACK;
return root;
}
//先按照AVL树的插入方式找到插入位置
RBNode* z=root;
RBNode* p=NULL;
while(z){
p=z;
if(k<z->data)z=z->l;
else z=z->r;
}//结束循环z为NULL
z=createNode(p,NULL,NULL,k);
//连接
if(k < p->data)p->l=z;
else p->r=z;
// -----调整----
//父亲p为黑,不破坏 不红红
if(z->pa->color==BLACK){
return root;
}else{
//p为红
root=fixUp(root,z);
return root;
}
}
//中序遍布验证红黑树正确与否
void inOrder(RBNode* node){
if(node==NULL)return;
inOrder(node->l);
printf("%d ",node->data);
if(node->color==RED)printf("RED ");
else printf("BLACK ");
if(node->pa)printf("%d",node->pa->data);
printf("\n");
inOrder(node->r);
}
//------------------删除--------------
RBTree DE_fixup(RBTree root,RBNode* x,RBNode* p){
if(x&&x->color==RED){
x->color=BLACK;
return root;
}
//x颜色为黑色条件下调整 x p(可以有空 但是不都为空 否则空树返回了)
//先保证x父亲p存在在保证x是黑色,如果x父亲不存在就根x一个黑节点就无需调整
while( x!=root&& (x==NULL||(x&&x->color==BLACK) ) ){
RBNode* w=NULL;//叔叔一定存在(黑路同)
//讨论叔叔w和x在左或右
//讨论w为黑红
//讨论w的孩子颜色
if(x==p->l){
w=p->r;
//w为红可转化为黑
if(w->color==RED){
root=leftRotate(root,p);
w->color=BLACK;
p->color=RED;
w=p->r;
}
//运行到此处w一定为黑色
//1.w为黑无红孩子就需要向上调整,p(新的x)不为红继续循环
//w为黑有红孩子就RL-->RR,统一调整RR和两个红孩子情况,结束循环
if( (w->l==NULL||(w->l&&w->l->color==BLACK))//1.w为黑两个黑孩子
&& (w->r==NULL||(w->r&&w->r->color==BLACK)) )
{
w->color=RED;
x=p;
p=x->pa;//需要向上循环(调整到x为红色需要跳出循环并且变红)
}
else//有红孩子
{
//RL(w左边是红孩子,右边为空)转化为RR
if(w->r==NULL){
root=rightRotate(root,w);
w->color=RED;
RBNode* red=w->l;
red->color=BLACK;
RBNode* t=NULL;
//交换red和w指针
t=red;
red=w;
w=t;
}
//运行到这里就是RR或者两个红孩子了,也可以当RR处理
RBNode* red=w->r;
w->color=p->color;
root=leftRotate(root,p);
red->color=BLACK;
p->color=BLACK;
break;//一次调整就好
}
}
else{//x==p->r
w=p->l;
//w为红可转化为黑
if(w->color==RED){
root=rightRotate(root,p);
w->color=BLACK;
p->color=RED;
w=p->l;
}
//运行到此处w一定为黑色
//1.w为黑无红孩子就需要向上调整,p(新的x)不为红继续循环
//w为黑有红孩子就LR-->LL,统一调整LL和两个红孩子情况,结束循环
if( (w->l==NULL||(w->l&&w->l->color==BLACK))//1.w为黑两个黑孩子
&& (w->r==NULL||(w->r&&w->r->color==BLACK)) )
{
w->color=RED;
x=p;
p=x->pa;//需要向上循环
}
else//有红孩子
{
//RL(w右边是红孩子,左边为空)转化为LL
if(w->l==NULL){
root=leftRotate(root,w);
w->color=RED;
RBNode* red=w->r;
red->color=BLACK;
RBNode* t=NULL;
//交换red和w指针
t=red;
red=w;
w=t;
}
//运行到这里就是LL或者两个红孩子了,也可以当LL处理
RBNode* red=w->l;
w->color=p->color;
root=rightRotate(root,p);
red->color=BLACK;
p->color=BLACK;
break;//一次调整就好
}
}
}
if(x&&x->color==RED)x->color=BLACK;
return root;
}
RBNode* findElem(RBNode* root,int k){
if(root==NULL||root->data==k)return root;
if(k<root->data)return findElem(root->l,k);
if(k>root->data)return findElem(root->r,k);
}
RBTree Delete(RBTree root,int k){
if(root==NULL)return root;
//删除先要先找到k
RBNode* y=findElem(root,k);
if(y==NULL){
printf("can not find k\n");
return root;
}
//如果数据k的节点y 度为二转化为1/0
if(y->l&&y->r){
//中序遍历后继
RBNode* t=y->r;
while(t->l){
t=t->l;
}
y->data=t->data;
y=t;//更新删除节点y为新的
}
//度为1/0直接处理y y父亲p(考虑是不是p为空) y唯一孩子x(可能为空)
//运行到这里就只有可能度为1/0
RBNode* p=y->pa;
RBNode* x=NULL;//要顶替y的后继x
if(y->l)x=y->l;
else x=y->r;
if(x)x->pa=p;
if(p){
if(p->l==y)p->l=x;
else p->r=x;
}else{
//如果y是根就把x变为新根
root=x;
}
int yc=y->color;
free(y);
if(root==NULL)return root;//删除后树为空无需调整
//删除后调整y的颜色为红无需调整,为黑调整
if(yc==BLACK){
root=DE_fixup(root,x,p);
}
return root;
}
int main(){
RBTree root=NULL;
int n;//内部节点个数
scanf("%d",&n);
int k;
while(n--){
scanf("%d",&k);
root=insert(root,k);
}
inOrder(root);
int x;
while(1){
scanf("%d",&x);
if(x==-1)break;
root=Delete(root,x);
inOrder(root);
}
return 0;
}
//总结
//一共 10 情况,a.1 和 b 可以由很多情况转化。
//可以转化写在前,先预处理