手撕红黑树

从0到1手撕红黑树

红黑树:一种自平衡的二叉查找树

一、性质 (平衡条件) (很重要

  1. 节点非黑即红
  2. 根节点是黑色
  3. 叶子节点(NIL)是黑色
  4. 红色节点连接的两个子节点均为黑色 -> (红色节点与红色节点不可以相连)
  5. 从根节点到所有叶子节点的路径上,黑色节点的数量相同

小推论:
性质4+性质5 => 最长边节点数量:最短边节点数量 = 2 :1
某种程度上来说,红黑树也是靠控制树高来控制平衡的

节点定义

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

引入NIL来代替NULL节点会让对红黑树的操作更加简单
NIL节点定义 + 初始化

Node __NIL;
#define NIL (&__NIL)
__attribute__((constructor))
void init_NIL() {
    
    
    NIL->key = 0;
    NIL->color = 1;
    NIL->lchild = NIL->rchild = NIL;
    return ;
}

重头戏

一、红黑树的插入:

  1. 插入节点选择红色节点 (插入红色节点未必需要调整,但是插入黑色节点,由于性质5,一定需要调整,所以插入调整其实就是为了解决双红的问题)
  2. 插入调整在祖父节点处进行(看子节点与孙子节点是否由双红节点冲突)

插入调整

情况一:
在这里插入图片描述
站在18来看,父节点是红色,且叔叔节点为红色
站在15来看,两个子节点是红色,并且,两个子节点中也有红色节点(出现双红现象)

为保持每条路径黑色节点数量相同== 本次采取红色上浮黑色下沉== 如图
修改三元组的颜色 -> 红黑黑

在这里插入图片描述

先不考虑根节点是黑色,将当前的每一棵树都当成一颗子树(这很重要))

情况二:
叔叔节点为黑色
参考AVL树的失衡情况,
分成LL,LR,RL,RR
根据AVL树的旋转调整策略,修改三元组的颜色
在这里插入图片描述
对于LL/RR 简单明了,进行大右旋 / 大左旋,调整三元组
本处为LL , 以15进行大右旋, 对三元组进行红色上浮
在这里插入图片描述

类比LL不难得出 :遇到RR进行大左旋,然后对三元组进行红色上浮

LR/RL:
这里采用LR举例
在这里插入图片描述
在15 处进行小左旋使该树变成LL情况
在这里插入图片描述
而后根据LL情况进行大右旋

同理 遇到RL时,先对子树进行小右旋,而后对整个树进行大左旋,然后对三元组进行调整

代码演示

Node *left_rotate(Node *root) {
    
    
    Node *temp = root->rchild;
    root->rchild = temp->lchild;
    temp->lchild = root;
    return temp;
}//左旋

Node *right_rotate(Node *root) {
    
    
    Node *temp = root->lchild;
    root->lchild = temp->rchild;
    temp->rchild = root;
    return temp;
}//右旋

插入调整代码

Node *insert_maintain(Node *root) {
    
    
    if (!has_red_child(root)) return root;
    if (root->lchild->color == 0 && root->rchild->color == 0 && (has_red_child(root->lchild) || has_red_child(root->rchild))) {
    
    
    //叔叔节点为红色节点
        root->color = 0;
        root->lchild->color = root->rchild->color = 1;
        return root;
    }
    int flag = 0;//用来判断双红冲突节点在左or右
    if (root->lchild->color == 0 && has_red_child(root->lchild)) flag = 1;//左红节点
    if (root->rchild->color == 0 && has_red_child(root->rchild)) flag = 2;//右红节点
    if (flag == 0) return root;
    if (flag == 1) {
    
    
        if (root->lchild->rchild->color == 0) {
    
    
            root->lchild = left_rotate(root->lchild);//LR小左旋
        }
        root = right_rotate(root);//LL大右旋
    } else {
    
    
        if (root->rchild->lchild->color == 0) {
    
    
            root->rchild = right_rotate(root->rchild);//RL小右旋
        }
        root = left_rotate(root);//RR大左旋
    }
        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 (key < root->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;
}

二、红黑树的删除

  1. 删除红色节点,不会对红黑树的平衡产生影响
  2. 度为1的黑色节点,唯一子孩子,一定时红色
  3. 删除度为1的黑色节点,不会产生删除调整(子节点颜色+1(由红色变为黑色,上来顶替该节点))
  4. 删除度为0的黑色节点,会产生一个双重黑的NIL节点(下面的一个NIL节点(黑色)颜色+1变为双重黑节点)
  5. 删除调整,就是为了干掉双重黑
  6. 删除调整从父节点进行调整

情况一:双重黑的兄弟节点是黑色

1.双重节点的兄弟节点是黑色,并且其兄弟节点的子节点没有红色
在这里插入图片描述
调整策略
双重黑节点与其兄弟节点颜色分别减一重黑,父亲节点增加一重黑色

2.双重节点的兄弟节点是黑色,但是其兄弟节点的子节点有红色
在这里插入图片描述
以RR举例
因为这里可以确定双重黑的兄弟节点一定为黑色节点,所以左旋89 的左子树,让双重黑的节点,作为根节点,来补上28 的二重黑,28得已减去一重黑可以保持每条路径上的黑色节点数量相同
28减一重黑调整位置任意

分析:
对于左旋之后的树(下图)
在旋转前后可以保证:
因28是双重黑,51是其兄弟节点,可以确定28和51的颜色,一定是黑
因为RR 可以判定72一定是红色, 而又根据红黑树的性质4,可以确定64 和85 一定是黑色
但是38 和 48 的颜色不确定,为了避免 38 和 48 可能引发的双红冲突,将38 和 72 强制改为黑色节点,而51跟随原根节点的颜色
从而48 的颜色随意,推出 => 当遇到双重黑节点的兄弟节点的子节点均为红时,按照RR或者LL来解决,因为另外节点无论时什么颜色,都不会产生影响

在这里插入图片描述
调整策略:
RR / LL 右旋 / 左旋, 新根改为原来根的颜色,将新根的两个子节点,改为黑色

以RL举例
在这里插入图片描述
对于RL / LR 我们的目的是将其转换为RR / LL 所以对 兄弟节点进行小右旋,原兄弟节点改为红色,新兄弟节点改为黑色

在这里插入图片描述

而后根据RR / LL 进行调整

3.特殊情况
兄弟节点为红色节点
在这里插入图片描述
调整策略:
抓双黑节点的父节点,向双黑节点方向进行旋转,原根节点改为红节点,新根节点改为黑节点
进行左旋 / 右旋 后,新根节点的左子节点 / 右子节点 为双重黑的父节点,所以站在双重黑的父节点上进行删除调整

删除调整代码

Node *erase_maintain(Node *root) {
    
    
    if (root->lchild->color != 2 && root->rchild->color != 2) return root;//从n本行以后,存在双重黑节点
    if (has_red_child(root)) {
    
    
        int flag = 0;//找到红节点在那个方向
        root->color = 0;//旧根节点颜色改为红色
        if (root->lchild->color == 0) {
    
    
            root = right_rotate(root);//向双重黑的方向旋转
            flag = 1;
        } else {
    
    
            root = left_rotate(root);//向双重黑的方向旋转
            flag = 2;
        }
        root->color = 1;//新根节点的颜色改为黑色
        if (flag == 1) root->rchild = erase_maintain(root->rchild);
        else root->lchild = erase_maintain(root->lchild);
        //站在双重黑的父节点处重新进行删除调整
        return root;
    }
    if ((root->lchild->color == 2 && !has_red_child(root->rchild)) ||
        (root->rchild->color == 2 && !has_red_child(root->lchild))) {
    
    //双重黑的兄弟节点为黑节点并且他的的子节点没有有红节点
            root->lchild->color -= 1;
            root->rchild->color -= 1;//兄弟节点减一层黑
            root->color += 1;//根节点加一层黑
            return root;
    }//情况一
    if (root->lchild->color == 2) {
    
    //双重黑在左子树节点上
        root->lchild->color -= 1;//双重黑节点减一层黑
        if (root->rchild->rchild->color != 0) {
    
    //RL情况下:先小右旋再大左旋
            root->rchild->color = 0;//原兄弟节点改为红色
            root->rchild = right_rotate(root->rchild);//小右旋
            root->rchild->color = 1;//新兄弟节点改为黑色
        }
        root = left_rotate(root);//大左旋
        root->color = root->lchild->color;//新根的颜色跟随就根的颜色
    } else {
    
    
        root->rchild->color -= 1;//双重黑节点减一层黑
        if (root->lchild->lchild->color != 0) {
    
    //LR情况下,小左旋
            root->lchild->color = 0;//原兄弟节点改为红色
            root->lchild = left_rotate(root->lchild);//小左旋
            root->lchild->color = 1;//新兄弟节点改为黑色
        }
        root = right_rotate(root);//大右旋
        root->color = root->rchild->color;//新根的颜色跟随就根的颜色
    }
    root->lchild->color = root->rchild->color = 1;//新根的子节点改为黑色
    return root;
}

删除部分代码

Node *__erase(Node *root, int key) {
    
    
    if (root == NIL) return NIL;
    if (key < root->key) {
    
    
        root->lchild = __erase(root->lchild, key);
    } else if (key > root->key) {
    
    
        root->rchild = __erase(root->rchild, key);
    } else {
    
    
        if (root->lchild == NIL || root->rchild == NIL) {
    
    //删除度为0或者1的节点
            Node *temp = root->lchild != NIL ? root->lchild : root->rchild;
            temp->color += root->color;//产生二重黑的原因
            free(root);
            return temp;
        } else {
    
    
            Node *temp = predecessor(root);
            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;
}

红黑树整体代码

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

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

Node __NIL;
#define NIL (&__NIL)
__attribute__((constructor))
void init_NIL() {
    
    
    NIL->key = 0;
    NIL->color = 1;
    NIL->lchild = NIL->rchild = NIL;
    return ;
}

void output(Node *);
Node *getNewNode(int key) {
    
    
    Node *p = (Node *)malloc(sizeof(Node));
    p->key = key;
    p->color = 0;
    p->lchild = p->rchild = NIL;
    return p;
}

int has_red_child(Node *root) {
    
    
    return root->lchild->color == 0 || root->rchild->color == 0;
}

Node *left_rotate(Node *root) {
    
    
    Node *temp = root->rchild;
    root->rchild = temp->lchild;
    temp->lchild = root;
    return temp;
}

Node *right_rotate(Node *root) {
    
    
    Node *temp = root->lchild;
    root->lchild = temp->rchild;
    temp->rchild = root;
    return temp;
}

Node *insert_maintain(Node *root) {
    
    
    if (!has_red_child(root)) return root;
    if (root->lchild->color == 0 && root->rchild->color == 0 && (has_red_child(root->lchild) || has_red_child(root->rchild))) {
    
    //双红节点,并且根节点也是红的时候
        root->color = 0;
        root->lchild->color = root->rchild->color = 1;
        return root;
    }
    int flag = 0;
    if (root->lchild->color == 0 && has_red_child(root->lchild)) flag = 1;//左红节点
    if (root->rchild->color == 0 && has_red_child(root->rchild)) flag = 2;//右红节点
    if (flag == 0) return root;
    if (flag == 1) {
    
    
        if (root->lchild->rchild->color == 0) {
    
    
            root->lchild = left_rotate(root->lchild);
        }
        root = right_rotate(root);
    } else {
    
    
        if (root->rchild->lchild->color == 0) {
    
    
            root->rchild = right_rotate(root->rchild);
        }
        root = left_rotate(root);
    }
        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 (key < root->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;
}

Node *predecessor(Node *root) {
    
    
    Node *temp = root->lchild;
    while(temp->rchild != NIL) temp = temp->rchild;
    return temp;
}

Node *erase_maintain(Node *root) {
    
    
    if (root->lchild->color != 2 && root->rchild->color != 2) return root;//从n本行以后,存在双重黑节点
    if (has_red_child(root)) {
    
    
        int flag = 0;
        root->color = 0;
        if (root->lchild->color == 0) {
    
    
            root = right_rotate(root);
            flag = 1;
        } else {
    
    
            root = left_rotate(root);
            flag = 2;
        }
        root->color = 1;
        if (flag == 1) root->rchild = erase_maintain(root->rchild);
        else root->lchild = erase_maintain(root->lchild);
        return root;
    }
    if ((root->lchild->color == 2 && !has_red_child(root->rchild)) ||
        (root->rchild->color == 2 && !has_red_child(root->lchild))) {
    
    //双重黑的兄弟节点为黑节点并且他的的子节点没有有红节点
            root->lchild->color -= 1;
            root->rchild->color -= 1;//兄弟节点减一层黑
            root->color += 1;//根节点加一层黑
            return root;
    }//情况一
    if (root->lchild->color == 2) {
    
    //双重黑在左子树节点上
        root->lchild->color -= 1;//双重黑节点减一层黑
        if (root->rchild->rchild->color != 0) {
    
    //RL情况下:先小右旋再大左旋
            root->rchild->color = 0;
            root->rchild = right_rotate(root->rchild);//小右旋
            root->rchild->color = 1;
        }
        root = left_rotate(root);//大左旋
        root->color = root->lchild->color;
    } else {
    
    
        root->rchild->color -= 1;//双重黑节点减一层黑
        if (root->lchild->lchild->color != 0) {
    
    
            root->lchild->color = 0;
            root->lchild = left_rotate(root->lchild);
            root->lchild->color = 1;
        }
        root = right_rotate(root);
        root->color = root->rchild->color;
    }
    root->lchild->color = root->rchild->color = 1;
    return root;
}

Node *__erase(Node *root, int key) {
    
    
    if (root == NIL) return NIL;
    if (key < root->key) {
    
    
        root->lchild = __erase(root->lchild, key);
    } else if (key > root->key) {
    
    
        root->rchild = __erase(root->rchild, key);
    } else {
    
    
        if (root->lchild == NIL || root->rchild == NIL) {
    
    //删除度为0或者1的节点
            Node *temp = root->lchild != NIL ? root->lchild : root->rchild;
            temp->color += root->color;//产生二重黑的原因
            free(root);
            return temp;
        } else {
    
    
            Node *temp = predecessor(root);
            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 print(Node *root) {
    
    
    printf("%d %d %d %d\n", root->key, root->color, root->lchild->key, root->rchild->key);
}

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

int main() {
    
    
    int op, val;
    Node *root = NIL;
    while(~scanf("%d%d", &op, &val)) {
    
    
        switch (op) {
    
    
            case 1: root = insert(root, val); break;
            case 2: root = erase(root, val); break;
            case 3: output(root);
        }
    //    output(root);
    //    printf("-----------\n");
    }
    return 0;
}

如果看明白了,不妨自己手撕一下 红黑树oj 检查一下qwq


代码中的找前驱代码为不完美代码,有bug 如果有什么改进的想法,欢迎各qwq

猜你喜欢

转载自blog.csdn.net/aMonstere/article/details/112645337