红黑树(含图解和代码参考)

  1. 红黑树的结构定义
  2. 红黑树的学习诀窍
  3. 红黑树的学前思考
  4. 红黑树的插入调整情况
  5. 红黑树的删除调整情况
  6. 红黑树的代码演示

1.红黑树——结构定义

五个条件:

(1)每个结点非黑即红

(2)根结点是黑色

(3)叶结点(NIL)是黑色

(4)如果一个结点是红色,则它的两个子结点都是黑色的(红色和红色不能挨着)

(5)从根结点出发到所有叶结点路径上,黑色结点数量相同

2.红黑树——学习诀窍

(1)插入调整站在==祖父结点==看

(2)删除调整站在==父结点==看

(3)插入(2)和删除(3)的情况处理一共总结为五种

(4)理解调整策略时,时刻记住黑色结点数量调整前后相同(对整体不影响)

3.红黑树——学前思考

qes1:红黑树中,最长路径和最短路径长度的关系?
在这里插入图片描述

qes2:新插入的结点是什么颜色的?红色,黑色?

ans2:红色。插入黑色一定失衡,插入红色不一定失衡。


4.插入调整情况

(1)情况一:叔叔是红色——最简单

(四种情况,x也可以在图示中的另外三个兄弟结点的位置)
在这里插入图片描述

(2)情况二:叔叔是黑色(LL、LR)

LL型为例:

①红黑黑:红色上浮

②黑红红:红色下沉
请添加图片描述

LR型:先进行小左旋,不需要进行颜色调整,再进行大右旋,最后才进行颜色调整(红色上浮、红色下沉)

5.删除调整

情况1:删除度为2的红色结点

情况2:删除度为2的黑色结点

(1)和(2)转换为删除度为1和度为0的情况,不另做讨论。

情况3:删除度为1的红色结点

不存在度为1的红色结点。因为从根结点出发到所有叶结点路径上,黑色结点数量相同。

情况4:删除度为1的黑色结点

度为1的黑色结点有且只有一个红色子结点。(只有倒数第二层会出现度为1的黑色结点)直接把红色子结点变为黑色结点即可。

情况5:删除度为0的红色结点

直接删除。

情况6:删除度为0的黑色结点

影响平衡。因为从根结点出发到所有叶结点路径上,黑色结点数量相同,删除一个黑叶子结点时,另一个黑色叶子结点就会无处安放。被删除的黑色结点连接的NIL结点就会被记为双重黑色。

删除调整,主要就是解决由双重黑造成的不平衡

(1)删除调整一

brother结点为黑色且无红色子结点
在这里插入图片描述

(2)删除调整二

兄弟结点为黑且其同一侧的子结点是红色结点,RR类型
在这里插入图片描述

(3)删除调整三

brother结点为黑色且红色子结点不在同一侧,则为RL类型
在这里插入图片描述

(4)删除调整四

兄弟结点为红色结点
在这里插入图片描述

6.代码演示

#include<stdio.h>
#include <stdlib.h>

typedef struct Node{
        int key, color; //color 0: red, 1: black, 2: double black
        struct Node *lchild, *rchild;

} Node;

Node __NIL;//NIL映射到一个真实结点的地址
#define NIL (&__NIL)

__attribute__((constructor))
void init_NIL(){
    NIL->key = 0;
    NIL->color = 1;//红黑树中NIL结点是黑色
    NIL->lchild = NIL->rchild = NIL;
    return ;
}

Node *getNewNode(int key){
    Node *p = (Node *)malloc(sizeof(Node));
    p->key = key;
    p->lchild = p->rchild = NIL;
    p->color = 0; //新插入结点默认是红色(插入黑色必定不平衡)
    return p;
}

//返回当前结点下面是否有红色结点
int has_red_child(Node *root){
    return root->lchild->color == 0 || root->rchild->color == 0;
}

//左旋
Node *left_rotate(Node *root){
    printf("left rotate: %d\n", root->key);
    Node *new_root = root->rchild; //新根结点=原根结点的右子结点
    root->rchild = new_root->lchild; //原根结点的右子结点=新根结点的左子结点
    new_root->lchild = root; //新根结点的左子结点=原根结点
    return new_root;
}

//右旋
Node *right_rotate(Node *root){
    printf("right_rotate: %d\n", root->key);
    Node *new_root = root->lchild; //新根结点=原根结点的左子结点
    root->lchild = new_root->rchild; //原根结点的左子结点=新根结点的右子结点
    new_root->rchild = root; //新根结点的右子结点=原根结点
    return new_root;
}

const char *insert_maintain_type_str[] = {"insert type 1: change color", "insert type 2: LL", "insert type 2: LR", "insert type 2: RR", "insert type 2: RL"};

Node *insert_maintain(Node *root){
    if(!has_red_child(root)) return root; //如果当前结点的子结点没有红色的,就不会出现双红的情况
    //左(右)子树颜色是红色并且左(右)子树没有红色子孩子时没有冲突
    if(!(root->lchild->color == 0 && has_red_child(root->lchild)) &&
       !(root->rchild->color == 0 && has_red_child(root->rchild))) return root;

    //rotate
    int type = 0;
    if(root->rchild->color == 1){//右子树颜色是黑色,说明冲突发生在L(LL、LR)
        if(root->lchild->rchild->color == 0){ // LR类型
            root->lchild = left_rotate(root->lchild); //抓住左子树进行小左旋
            ++type;
        }
        ++type;
        root = right_rotate(root); //抓住根结点大右旋 LL
    } else if(root->lchild->color == 1){ //左子树的颜色是黑色,说明冲突发生在R(RR、RL)
        type = 2;
        if(root->rchild->lchild->color == 0){ //RL类型
            root->rchild = right_rotate(root->rchild); //抓住右子树进行小右旋
            ++type;
        }
        ++type;
        root = left_rotate(root); //抓住根结点大左旋 RR
    }

    printf("insert maintain type = %s\n", insert_maintain_type_str[type]);
    //上三角变成红黑黑
    root->color = 0;
    root->lchild->color = root->rchild->color = 1;
    return root;

}

Node *__insert(Node *root, int key){
    if(root == NIL) return getNewNode(key);
    if(root->key == key) return root;
    if(root->key > key) root->lchild = __insert(root->lchild, key);
    else root->rchild = __insert(root->rchild, key);
    return insert_maintain(root); //返回进行调整以后的根结点
}

Node *insert(Node *root, int key){
    root = __insert(root, key);
    root->color = 1; //保证根结点是黑色
    return root;
}

const char *erase_maintain_type_str[] = {
    "red 1(right) : change color",
    "red 2(left) : change color",
    "balck 1 : no red child", 
    "black 2 : LL",
    "black 2 : LR", 
    "black 2 : RR",
    "black 2 : RL"
};

//删除调整
Node *erase_maintain(Node *root){
    if(root->lchild->color != 2 && root->rchild->color != 2) return root; //左右子树都不是双重黑,直接return
    int type = 0;
    if(has_red_child(root)){ //如果当前结点下有红色子结点,就是双重黑结点的兄弟结点是红色
        root->color = 0; //原根结点改为红色
        if(root->lchild->color == 0){ //如果左侧的结点是红色
            root = right_rotate(root); //拉着当前根结点进行右旋,双黑结点就会在右子树上
        } else { //否则拉着根结点进行左旋,双黑结点就会在左子树上
            root = left_rotate(root);
            type  = 1;
        }
        root->color = 1; //旋转之后的新根结点改为黑色
        if(type) root->lchild = erase_maintain(root->lchild); //type是1,递归到左子树去进行删除调整
        else root->rchild = erase_maintain(root->rchild); //否则到右子树进行删除调整
        printf("brother is %s\n", erase_maintain_type_str[type]);
        return root;
    }

    //处理双黑结点的兄弟结点是黑色且无红色子结点(删除调整一)
    if((root->lchild->color == 1 && !has_red_child(root->lchild)) ||
       (root->rchild->color == 1 && !has_red_child(root->rchild))){
           type = 2;
           --root->lchild->color; //左子结点-1层黑色
           --root->rchild->color; //右子结点-1层黑色
           ++root->color; //根结点+1层黑色
           return root;
       }

    type = 2;
    //调整二、三
    if(root->rchild->color == 1){ //如果左子树的颜色是黑色 L
        if(root->lchild->lchild->color != 0){ // LR类型(要直接判断左子树的颜色,因为LL类型是优先的,右子 树不论是不是红色,只要左子树是红色就是LL类型)
            ++type;
            root->lchild = left_rotate(root->lchild); //抓住左子树进行小左旋
        }
        ++type;
        root->rchild->color = 1;
        root->lchild->color = root->color; //新根结点的颜色改为原根结点的原色。右旋的新根结点就是原来的左 子树
        root = right_rotate(root); //抓住当前根结点进行大右旋
    } else { //LL
        type = 3;
        if(root->rchild->rchild->color != 0){
            ++type;
            root->rchild = right_rotate(root->rchild);
        }
        ++type;
        root->rchild->color = root->color;
        root = left_rotate(root);
    }
    root->lchild->color = 1;
    root->lchild->color = root->rchild->color = 1; //根结点的左右子结点都改为黑色
    printf("brother is %s\n", erase_maintain_type_str[type]);
    return root;
}

Node *__erase(Node *root, int key){
    if(root == NIL) return root;
    if(root->key > key) root->lchild = __erase(root->lchild, key); //到左子树删除
    else if(root->key < key) root->rchild = __erase(root->rchild, key); //到右子树删除
    else { //要删除的是当前结点
        if(root->lchild == NIL || root->rchild == NIL){ //删除n = 0 || n = 1的,对应了情况3、4、5、6
            Node *temp = root->lchild == NIL ? root->rchild : root->lchild; //如果左子树为空就指向右子树,否则指向左子树。找到唯一子孩子或者让temp指向NIL
            //情况4:红色子结点变为黑色代替被删除的黑色结点。红色是0,黑色是1,把当前子结点的黑色加到子结 点上去
            //情况5直接删除,不用管颜色
            //情况6:temp结点指向的其实是NIL结点,把当前结点的黑色加到NIL上去变成双重黑
            temp->color += root->color; 
            free(root);
            return temp;
        } else { //n = 2
            Node *temp = root->lchild;
            while(temp->rchild != NIL) temp = temp->rchild; //找到当前结点的前驱(也可以是后继)
            root->key = temp->key; //前驱的值直接赋值给当前结点
            root->lchild = __erase(root->lchild, temp->key); //到左子树中删除前驱结点的值
        }
    }
    return erase_maintain(root); 
}

Node *erase(Node *root, int key){
    root = __erase(root, key);
    root->color = 1; //保证根结点是黑色
    return root;
}

void clear(Node *root){
    if(root == NIL) return ;
    clear(root->lchild);
    clear(root->rchild);
    free(root);
    return ;
}

void printf_node(Node *root){
    printf("( %d(%d) | %d, %d )\n", root->key, root->color, root->lchild->key, root->rchild->key);
    return ;
}

void output(Node *root){
    if(root == NIL) return ;
    printf_node(root);
    output(root->lchild);
    output(root->rchild);
    return ;
}

int main(){
    Node *root = NIL;

    int key;
    while(~scanf("%d", &key)){
        if(key == -1) break;
        printf("\n===insert %d to rad black tree===\n", key);
        root = insert(root, key);
        output(root);
    }
    while(~scanf("%d", &key)){
        if(key == -1) break;
        printf("\n===erase %d to rad black tree===\n", key);
        root = erase(root, key);
        output(root);
    }

    clear(root);

    return 0;
}

要是对您有帮助,点个赞再走吧~ 欢迎评论区讨论~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值