终于到红黑树了,红黑树在软件中使用是比较多的,也是一个重点难点,接下来就慢慢啃掉这个红黑树。
7.1 红黑树的性质
7.1.1 引入红黑树
前面有讲了平衡二叉树,平衡二叉树是一个强平衡,要保证每个结点的平衡因子都不大于1,就因为是强平衡,导致性能不是很好,所以才有红黑树的出现,红黑树是一个弱平衡。
红黑树是在每个结点上添加一个存储位来表示结点的颜色,可以是RED或BLACK,通过对任何一条从根结点到叶子的简单路径上各个结点的颜色进行约束,红黑树确保没有一条路径会比其他路径长出2倍,因而进似平衡。
7.1.2 红黑树的性质
一棵红黑树是满足下面红黑性质的二叉搜索树:
- 每个结点或是红色的,或是黑色的
- 根结点是黑色的
- 每个叶结点(NIL)是黑色的
- 如果一个结点是红色的,则它的两个子结点都是黑色的
- 对每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
7.1.3 nil结点
为了便于处理红黑树代码中的边界条件,使用了一个nil结点来代替NIL,所有指向NIL的指针都指向了nil结点。
7.1.4 黑高
从某个结点x出发(不包含该结点)到达一个叶结点的任意一条简单路径上的黑色结点个数称为该结点的黑高(black-high),记为bh(x)。
7.1.5 红黑树图
这个是红黑树的图,满足上面的性质,图片来源是Kind老师课件上的。
7.1.6 红黑树的结构
typedef int Elemtype;
typedef enum
{
RED = 0,
BLACLK,
}_color;
#define RBTREE_ENTRY(name, type) \
struct name \
{ \
struct type *left; \
struct type *right; \
struct type *parent; \
_color color; \
}
typedef struct rbTree_node
{
Elemtype data; //结点数据
RBTREE_ENTRY(, rbTree_node) rbst; //红黑树结点信息
}_rbTree_node;
typedef struct rbTree
{
struct rbTree_node *root; //指向根结点
struct rbTree_node *nil; //nil结点
}_rbTree;
红黑树的结点多了双亲结点和颜色结点
7.2 红黑树的旋转
7.2.1 旋转的说明
平衡二叉树的时候旋转讲的很详细了, 因为那时候刚接触,看了好几遍才能看懂,现在一看就懂了,所以只是简单的讲解就可以了。
先上图:
左右旋就一个图可以搞定的,当初在写平衡二叉树的时候, 不清楚,现在清楚了,就用一个图。
左旋:
x结点变成了y的左孩子,y的左孩子变成了x的右孩子,x的父结点的孩子指向y。
右旋:
y结点变成了x的右孩子,x的右孩子变成了y的左孩子,y的父结点的孩子指向x。
7.2.2 旋转的代码
左旋:
/**
* @brief 左旋
* @param
* @retval
*/
static int rbTree_left_Rotate(struct rbTree *T, struct rbTree_node *x)
{
//获取x的右孩子y
struct rbTree_node *y = x->rbst.right;
//1.调整x父结点的孩子指向y,y的parent指针执行x的父
y->rbst.parent = x->rbst.parent;
if(x->rbst.parent == T->nil) { //注意这个nil结点
T->root = y;
} else if(x == x->rbst.parent.left) {
x->rbst.parent.left = y;
} else if(x == x->rbst.parent.right) {
x->rbst.parent.right = y;
}
//2.y左孩子挂在x右孩子上
x->rbst.right = y->rbst.left;
if(y->rbst.left != T->nil) {
y->rbst.left->bst.parent = x;
}
//3.x成为y的左孩子
y->rbst.left = x;
x->rbst.parent = y;
return 0;
}
右旋:
/**
* @brief 右旋
* @param
* @retval
*/
static int rbTree_right_Rotate(struct rbTree *T, struct rbTree_node *y)
{
//获取y的左孩子x
struct rbTree_node *x = y->rbst.left;
//1.调整y父结点的孩子指向x,x的parent指针执行x的父
x->rbst.parent = y->rbst.parent;
if(y->rbst.parent == T->nil) { //注意这个nil结点
T->root = x;
} else if(y == y->rbst.parent.left) {
y->rbst.parent.left = x;
} else if(y == y->rbst.parent.right) {
y->rbst.parent.right = x;
}
//2.x右孩子挂在y左孩子上
y->rbst.left = x->rbst.right;
if(x->rbst.right != T->nil) {
x->rbst.right->bst.parent = y;
}
//3.y成为x的右孩子
x->rbst.right = y;
y->rbst.parent = x;
return 0;
}
旋转并不难,只要把3个指向转换的关系理清楚就好。
7.3 红黑树的插入
到红黑树的插入了,说实话,插入操作比删除操作简单很多,因为插入操作是一个结点一个结点插入的,再插入之前这个红黑树就已经平衡了,在插入的时候,调整的范围也比较少。
7.3.1 创建结点
因为我们知道红黑树的结点是有两种颜色的,一种是RED,一种是BLACK,但是我们在创建结点的时候,这个结点的颜色是选择RED还是BLACK,下面先看一个图:
还是用这个图,我们在4结点的时候插入右子树,是不是感觉插入红色结点就不需要调整了,在673结点插入左子树的时候,是不是也是插入红色结点就不要调整了,但是如果插入的是黑色结点,不管在哪个位置插入都需要调整,因为插入黑色结点就会违背性质5,本来之前的红黑树的黑高是一样的,你插入了黑色结点之后黑高就肯定不一样了,所以插入结点我们选择插入红色。
创建结点的代码:
/**
* @brief 创建结点
* @param
* @retval
*/
static struct rbTree_node* rbTree_creat_node(struct rbTree *T, Elemtype data, _color color)
{
//申请一个结点
struct rbTree_node *node = (struct rbTree_node *)malloc(sizeof(struct rbTree_node));
assert(node);
//填充数据
node->data = data;
node->bst.color = color;
node->bst.left = T->nil;
node->bst.right = T->nil;
node->bst.parent = T->nil;
return node;
}
比较简单,就是单纯的创建结点,然后赋值。
7.3.2 插入结点
插入结点这个也比较简单,主要按照,插入的结点的data值遍历一下整个红黑树,然后找到要插入的位置,再调整一个这个位置的指针,把这个新的结点的插入到红黑树即可。插入的结点是否满足红黑树的性质,需要怎么调整,我们下节再分析:
/**
* @brief 红黑树插入操作
* @param
* @retval
*/
static int rbTree_Insert(struct rbTree *T, struct rbTree_node *z)
{
struct rbTree_node *y = T->nil; //y是x的父节点
struct rbTree_node *x = T->root; //x是查找当前要插入的结点
//循环遍历找到要插入的位置
while(x != T->nil)
{
y = x;
if(z->data < x->data) { //数据小于data,往左子树
x = x->rbst.left;
} else if(z->data > x->data) {
x = x->rbst.right;
}else { //两个数相等退出
return -1;
}
}
//把z挂接到y的孩子上
z->rbst.parent = y; //把z的父结点指针指向y
if(z->rbst.parent == T->nil) { //如果z的父节点为nil,说明现在是空树
T->root = z;
} else if(z->data < y->data ) { //数据比根结点数据小,挂在左子树上
y->rbst.left = z;
} else {
y->rbst.right = z;
}
//调整,下节讲
return 0;
}
7.3.3 调整结点
这个就比较难了,因为要涉及到调整,这里我们一步步插入,这样会更熟悉:
原始数据:{24,25,13,35,23, 26,67,47,38,98, 20,19,17,49,12, 21,9,18,14,15}
-
结点24
我们插入的是红色结点,这个需要调整成黑色。 -
结点25,结点13
这两个结点为红色,因为父结点为黑色,所以补需要调整 -
结点35
插入35结点是红色的,这就违背了红黑树的性质4,所以需要调整,这时候的叔父结点是红色的,
调整操作:父结点和叔父结点同时变成黑色,因为之前是红色,所以如果有子结点都是黑色,不会违背性质4和性质5,祖父结点变成红色,z的指针往上移动两层,继续遍历,根结点变成黑色。
调整后:
又满足了红黑树的性质。 -
结点23
-
结点26
这个结点26就比较复杂了,因为结点26的叔父结点是黑色的,25的左孩子没有的话就是指向nil,nil就是黑色的,并且这个结点26是左孩子,所以需要右旋:
现在这样就变成另外一个情况了,叔父结点为黑色,自己为右孩子,这样的处理方式是把父结点变成黑色,祖父结点变成红色,然后以祖父结点为轴心左旋,然后就可以符合红黑树的性质了 -
结点38
中间的结点自己模拟插入就可,上面已经讲过了
38这里有了子树,看起来比较多,不多操作也是一样的,
叔父结点是黑色,并且自己是右孩子,开始变颜色,并左旋 -
最后
其他结点我就不画了,插入到之后会形成这样的一个红黑树
看图确实还是挺平衡的,这里有一个连接,是可以看到红黑树的插入调整步骤的网页,学习的时候可以借助这个工具
更多参考代码: http://github.com/wangbojing
好像我的本地的,这里贴它github的连接
代码:
/**
* @brief 红黑树插入调整
* @param
* @retval
*/
static int rbTree_insert_fixup(struct rbTree *T, struct rbTree_node *z)
{
//父结点是nil结点,比较父结点是红色的,需要调整
while(z->rbst.parent->rbst.color == RED)
{
//叔父结点是在右子树上
if(z == z->rbst.parent->rbst.parent->rbst.left) {
//判断叔父结点的颜色
struct rbTree_node *y = z->rbst.parent->rbst.parent->rbst.right;
if(y->rbst.color == RED) {
//父结点和叔父结点同时变成黑色,祖父结点变成红色,z的指针往上移动两层,继续遍历
z->rbst.parent->rbst.color = BLACK;
y->rbst.color = BLACK;
z->rbst.parent->rbst.parent->rbst.color = RED;
z = z->rbst.parent->rbst.parent;
} else {
if(z == z->rbst.parent->rbst.right) { //叔父结点是黑色并且z是右孩子
z = z->rbst.parent;
rbTree_left_Rotate(T, z);
}
z->rbst.parent->rbst.color = BLACK; //叔父结点为黑色并且z是左孩子
z->rbst.parent->rbst.parent->rbst.color = RED;
rbTree_right_Rotate(T, z->rbst.parent->rbst.parent);
}
}else {
//判断叔父结点的颜色
struct rbTree_node *y = z->rbst.parent->rbst.parent->rbst.left;
if(y->rbst.color == RED) {
//父结点和叔父结点同时变成黑色,祖父结点变成红色,z的指针往上移动两层,继续遍历
z->rbst.parent->rbst.color = BLACK;
y->rbst.color = BLACK;
z->rbst.parent->rbst.parent->rbst.color = RED;
z = z->rbst.parent->rbst.parent;
} else {
if(z == z->rbst.parent->rbst.left) { //叔父结点是黑色并且z是左孩子
z = z->rbst.parent;
rbTree_right_Rotate(T, z); //左旋后,变成是右孩子了
}
z->rbst.parent->rbst.color = BLACK; //叔父结点为黑色并且z是右孩子
z->rbst.parent->rbst.parent->rbst.color = RED;
rbTree_left_Rotate(T, z->rbst.parent->rbst.parent);
}
}
}
T->root->rbst.color = BLACK;
return 0;
}
7.4 红黑树的其他函数
在说删除之前,先补充几个其他函数,因为这几个函数在删除的时候,是要用到的,谁叫删除那么难。
7.4.1 最小结点函数
红黑树也是排序二叉树,所以在这个树里面,最小的值就是在最左边,所以只需要不断的查找最左边的值,即可找到最小值。
代码:
/**
* @brief 查找x为根结点的子树的最小结点
* @param
* @retval
*/
static struct rbTree_node *rbTree_Min(struct rbTree *T, struct rbTree_node *x)
{
while(x->bst.left != T->nil)
{
x = x->bst.left;
}
return x;
}
7.4.2 最大结点函数
有最小结点就有最大结点,操作刚好是相反的。
代码:
/**
* @brief 查找x为根结点的子树的最大结点
* @param
* @retval
*/
static struct rbTree_node *rbTree_Max(struct rbTree *T, struct rbTree_node *x)
{
while(x->bst.right != T->nil)
{
x = x->bst.right;
}
return x;
}
7.4.3 搜索结点函数
传入data数据,匹配找到需要删除的结点,这是删除的时候需要的
/**
* @brief 查找x为根结点的子树的最大结点
* @param
* @retval
*/
static struct rbTree_node *rbTree_search(struct rbTree *T, Elemtype data)
{
struct rbTree_node *node = T->root;
while(node != T->nil)
{
if(data < node->data) {
node = node->rbst.left;
} else if(data > node->data) {
node = node->rbst.right;
}else {
return node;
}
}
return T->nil;
}
7.4.4 查找x结点的直接后继
在二叉树删除的时候,如果删除这个结点有两个子树的情况下,就需要寻找直接后继或者是直接前继,这里就封装成一个函数了,删除的时候可以直接使用。
/**
* @brief 查找x为根结点的子树的最大结点
* @param
* @retval
*/
static struct rbTree_node *rbTree_successor(struct rbTree *T, rbTree_node *x)
{
struct rbTree_node *y = x->rbst.parent;
//如果有右子树的话,就直接找右子树的最小值,就是直接后继
if(x->rbst.right != T->nil) {
return rbTree_Min(T, x->rbst.right);
}
//如果没有右子树,就往父结点走,当遇到一个为左子树的时候,这个时候的父结点就是直接后继
while((y != T->nil) && (x == y->rbst.right)) {
//后面的判断条件如果是left的话,就说明找到了直接后继
x = y;
y = y->rbst.parent;
}
return y;
}
这样一写,感觉当初写的排序二叉树,感觉有点不靠谱,以后有空再回来重写一遍。
7.5 红黑树的删除
插入操作还算简单了,就是删除操作比较难,不过再难也要逼着学会,因为红黑树确实很有用。
7.5.1 删除结点
红黑树的删除和二叉树的删除一样,要删除的结点也是分为3种情况:
-
没有左右子树
没有左右子树的情况下,就直接删除。 -
有左子树或者右子树
直接用子树替代要删除的结点。 -
有左子树且有右子树
这个就有点难受,删除最难受的就是这种情况,要找到被删除元素的直接后继,然后用这个直接后继替换要删除的元素,原来的直接后继的子树替换成直接后继
代码:
/**
* @brief 红黑树插入操作
* @param x结点替换y结点
* @retval
*/
static int rbTree_replace(struct rbTree *T, struct rbTree_node *y, struct rbTree_node *x)
{
if(y->rbst.parent == T->nil) { //y是根结点
T->root = x;
} else if(y == y->rbst.parent->rbst.left) { //x是y的左孩子
y->rbst.parent->rbst.left = x;
} else if(y == y->rbst.parent->rbst.right) { //x是y的右孩子
y->rbst.parent->rbst.right = x;
}
x->rbst.parent = y->rbst.parent;
return 0;
}
/**
* @brief 红黑树删除操作
* @param
* @retval
*/
int rbTree_Delete(struct rbTree *T, struct rbTree_node *z)
{
struct rbTree_node *y = z; //y是指向要被删除的结点或者是移动的结点
struct rbTree_node *x = T->nil;
_color y_old_color = y->rbst.color; //保存y以前的颜色
if(z->rbst.left == T->nil) { //删除的z没有左孩子
x = z->rbst.right; //保存x结点,x结点也是有移动过的
rbTree_replace(T, z, z->rbst.right); //用x替换到y
} else if(z->rbst.right == T->nil) {
x = z->rbst.left; //保存x结点,x结点也是有移动过的
rbTree_replace(T, z, z->rbst.left); //用x替换到y
} else {
y = rbTree_successor(T, z); //查找直接后继
y_old_color = y->rbst.color;
x = y->rbst.right;
if(z == y->rbst.parent) { //如果y就是z的孩子
x->rbst.parent = y;
} else {
rbTree_replace(T, y, y->rbst.right); //先把y结点给替换掉
y->rbst.right = z->rbst.right; //把z的右子树挂在到y的右子树上
y->rbst.right->rbst.parent = y;
}
rbTree_replace(T, z, y); //用x替换到y
//y结点替换z结点
y->rbst.left = z->rbst.left;
y->rbst.left->rbst.parent = y;
y->rbst.color = z->rbst.color;
}
//调整
free(z);
if(y_old_color == BLACK)
rbTree_delete_fixup(T, x);
return 0;
}
代码中的分情况已经很明确了,每一种情况都是一个if,只要细心观看就没有问题。
7.5.2 需要调整的情况
如果y是红色的
- 前面两种情况下,y为父结点的时候,孩子结点一定有两个,要不然黑高不平衡,所以不考虑
- 最后一种有两个子树的时候,y是z的孩子结点,y替换z,x替换y,如果y是红色的,那z是黑色的,x也是黑色的,到时候y会替换z的位置,颜色也变成黑色。黑高没有改变,也没有相连的两个红色结点。
- y不是z的孩子结点的时候,原来的y的右孩子x,会代替y,y是红结点,所以x是黑结点,不存在。
- y是红色结点,所以不会是根结点。
如果y是黑色的,会有以下几点破坏性质
- 原来y就是根结点,y的一个红色的孩子成为新的根结点
- 如果x和x.p都是红色的,违反了性质4
- y在树中移动的之后,相等于把y给删除了,这时候y的所有祖先都的黑高都小1,不平衡。
7.5.3 删除调整
按照插入的方式,把删除过一遍,希望过了这一边,就知道删除的详细信息了。
删除操作,是先删除掉结点,然后再进行调整
- 删除24结点
原始数据:
删除24结点:
删除24结点,然后24结点的后继结点25,替换掉24结点,如图
替换完成之后,跟w是左孩子时的情况4是一样的
w为左孩子时情况4:x的兄弟结点是w黑色结点,且w的左孩子是红色的
操作如下:
w->rbst.color = x->rbst.parent->rbst.color; //x的父节点的颜色赋值给w结点的颜色
x->rbst.parent->rbst.color = BLACK; //x的父节点的颜色赋值成黑色
w->rbst.left->rbst.color = BLACK; //w的左孩子的颜色赋值成黑色
rbTree_right_Rotate(T, x->rbst.parent); //然后以x的父节点为轴心右旋
结果如图:
2. 删除25结点
删除25结点,虽然25结点是叶子结点,没有左右子树,但是因为要删除的这个结点y是黑色的,这样一删除的话,树的黑高的不一样了,所以需要调整。
删除后,调整之前,会把25的右孩子替换掉25,这里的25的右孩子就是nil结点,因为nil是黑色的,w是21结点也是黑色的,21的左右孩子都是nil,也就是都是黑色的,这个符合w为左孩子时的情况2
w为左孩子时情况2:x的兄弟结点是w黑色结点,而且w的两个子结点都是黑色的
操作如下:
w->rbst.color = RED; //因为x少了一个黑色结点,所以要把w的这边也少一个黑色
x = x->rbst.parent; //再把x的指针往上移动,但是现在这个x还是确实一个黑色,还要继续调整
这是做了一次操作之后,这是x=23结点,然后这次符合了w为左孩子时的情况4,操作细节看上面,这里直接看结果:
丑是丑了点了,但是结果还是可以达到红黑树的性质的
-
删除13
这个简单就不画了 -
删除结点35
因为35结点只有一个孩子,所以35结点的孩子替换35结点,
但是因为35结点是y也是黑色的,所以调整,但是因为x是红色的,所以只要把x设置为黑色即可满足性质,这个就解决了违背性质4的问题。 -
删除结点23
这个跟上面的一样就不写了 -
删除结点26
终于对根结点动手了, 这次看看删除根结点有什么情况:
要把26结点删除,就要找到26结点的直接后继,这里的26结点的直接后继是38结点,使用38替换结点26
这时候的x是47结点的左孩子,所以w是67结点,这个还是符合w为右孩子是的情况4,具体就不写,结果如下: -
删除结点67
省略 -
删除结点47
这次来了一个有看头的了,删除结点47,47的直接后继结点49,直接替换47,结果:
这时候符合情况2,具体操作不画了,
这是x是指向49结点的,w是指向17结点,符合情况1
w为左孩子是情况1:x的兄弟结点w是红色的
操作如下:
w->rbst.color = BLACK; //把w设成黑色的
x->rbst.parent->rbst.color = RED; //把x的父节点设置成红色
rbTree_right_Rotate(T, x->rbst.parent); //然后进行右旋
w = x->rbst.parent->rbst->left; //调整w
调整完的结果:
这时候又符合情况2,所以继续调整
这时候就是x和x.p都为红色了,把x.p改为黑色,即可完成平衡。
-
删除好多结点
中间删除的结点可以省略,操作都差不多 -
删除结点21
原来的树:
这种情况符合情况1,操作完成之后
这种情况终于符合w为左孩子时,左孩子为黑色结点
w为左孩子时情况3:x的兄弟结点w是黑色的,w的左孩子是黑色的,w的右孩子是红色的
操作如下:
w->rbst.right->rbst.color = BLACK;
w->rbst.color = RED;
rbTree_left_Rotate(T, w);
w = x->rbst.parent->rbst.left;
操作完之后:
这种情况又回到了情况4,按情况4操作即可得到:
红黑树再次平衡。
还剩下5个结点我这里就不删除了,前面已经把这么多情况已经描述清楚了,后面也是这样了。
7.5.4 调整代码
/**
* @brief 红黑树插入调整
* @param
* @retval
*/
static int rbTree_delete_fixup(struct rbTree *T, struct rbTree_node *x)
{
while(x != T->root && x->rbst.color == BLACK)
{
if(x == x->rbst.left) { //x是左孩子
struct rbTree_node *w = x->rbst.parent->rbst.right;
if(w->rbst.color == RED) { //情况1:x的兄弟结点w是红色的
w->rbst.color = BLACK;
x->rbst.parent->rbst.color = RED;
rbTree_left_Rotate(T, x->rbst.parent);
w = x->rbst.parent->rbst->right;
}
if(w->rbst.left->rbst.color == BLACK && w->rbst.right->rbst.color == BLACK) {
//情况2:x的兄弟结点w是黑色的,而且w的两个子结点都是黑色的。
w->rbst.color = RED;
//因为x少了一个黑色结点,所以要把w的这边也少一个黑色
x = x->rbst.parent;
//再把x的指针往上移动,但是现在这个x还是确实一个黑色,还要继续调整
} else {
if(w->rbst.right->rbst.color == BLACK) {
//情况3:x的兄弟结点是w黑色结点,w的右孩子是黑色的
w->rbst.left->rbst.color = BLACK;
w->rbst.color = RED;
rbTree_right_Rotate(T, w);
w = x->rbst.parent->rbst.right;
}
//情况4:x的兄弟结点是w黑色结点,且w的右孩子是红色的
w->rbst.color = x->rbst.parent->rbst.color;
//x的父节点的颜色赋值给w结点的颜色
w->rbst.parent->rbst.color = BLACK; //x的父节点的颜色赋值成黑色
w->rbst.right->rbst.color = BLACK; //w的右孩子的颜色赋值成黑色
rbTree_left_Rotate(T, x->rbst.parent); //然后以x的父节点为轴心右旋
x = T->root;
}
} else if(x == x->rbst.right) { //x是右孩子
struct rbTree_node *w = x->rbst.parent->rbst.left;
if(w->rbst.color == RED) { //情况1:x的兄弟结点w是红色的
w->rbst.color = BLACK;
x->rbst.parent->rbst.color = RED;
rbTree_right_Rotate(T, x->rbst.parent);
w = x->rbst.parent->rbst->left;
}
if(w->rbst.left->rbst.color == BLACK && w->rbst.right->rbst.color == BLACK) {
//情况2:x的兄弟结点w是黑色的,而且w的两个子结点都是黑色的。
w->rbst.color = RED;
//因为x少了一个黑色结点,所以要把w的这边也少一个黑色
x = x->rbst.parent;
//再把x的指针往上移动,但是现在这个x还是确实一个黑色,还要继续调整
} else {
if(w->rbst.left->rbst.color == BLACK) {
//情况3:x的兄弟结点是w黑色结点,w的左孩子是黑色的
w->rbst.right->rbst.color = BLACK;
w->rbst.color = RED;
rbTree_left_Rotate(T, w);
w = x->rbst.parent->rbst.left;
}
//情况4:x的兄弟结点是w黑色结点,且w的左孩子是红色的
w->rbst.color = x->rbst.parent->rbst.color;
//x的父节点的颜色赋值给w结点的颜色
x->rbst.parent->rbst.color = BLACK; //x的父节点的颜色赋值成黑色
w->rbst.left->rbst.color = BLACK; //w的左孩子的颜色赋值成黑色
rbTree_right_Rotate(T, x->rbst.parent); //然后以x的父节点为轴心右旋
x = T->root;
}
}
}
x->rbst.color = BLACK;
return 0;
}
有一个可以在线插入删除红黑树的连接,学习的时候可以使用这个连接,看看红黑树的变化。
https://www.cs.usfca.edu/~galles/visualization/RedBlack.html