红黑树删除(C语言)

文章目录

  • 红黑树删除
    • 概念
    • 步骤
      • [按照 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 为红色

​ 调整方法二: 分类讨论:

  1. 父节点黑: 对 p 右旋,red 变黑
  2. 父节点红就只需要对 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,继续循环向上调整、

c 复制代码
w->color = RED;   // 第1步:兄弟变红(兄弟子树黑高减1)
x = p;            // 第2步:双黑结点上移给父亲
p = x->pa;        // 第3步:更新父亲指针,为下一轮循环做准备
为什么这么做?(物理意义)
  • 当前状态x 这边欠一层黑(双黑),兄弟 w 那边不欠(黑高正常)。
  • 目标:让两边平衡。
  • 操作
    1. 把兄弟 w 涂红 → 兄弟子树的黑高 减 1(现在它也"欠"一层了)。
    2. xw 各"抠"出一层黑,合成一层正常的黑色,还给父亲 p
    3. 此时父亲 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 黑/红的操作)

​ 调整方法二: 分类讨论:

  1. 父节点黑: 对 p 左旋,red 变黑

  2. 父节点红就只需要对 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 可以由很多情况转化。
//可以转化写在前,先预处理
相关推荐
不如语冰2 小时前
AI大模型入门-Python进阶-上下文管理与with语句
开发语言·数据结构·数据库·人工智能·pytorch·redis·python
Angle.寻梦3 小时前
数据结构--链表
数据结构·链表
ykdcdc3 小时前
DC-DC隔离和非隔离怎么选?工控/监控/医疗分别用哪种
数据结构·汽车·推荐算法
Angle.寻梦4 小时前
数据结构--堆
数据结构
ziguo11225 小时前
C/C++ 错误处理全解:从 errno 到 C++ 异常
linux·c语言·c++·windows·visual studio
雨落在了我的手上7 小时前
Java数据结构(六):链表的介绍
java·开发语言·数据结构
Angle.寻梦8 小时前
数据结构--栈与队列
数据结构
热爱前端的小张8 小时前
第六章 图
数据结构
小+不通文墨8 小时前
--no-multibyte-chars 解决OLED_data.c移植后中文乱码问题
c语言·经验分享·笔记·学习