一、简介
红黑树是一种相当巧妙的数据结构,它比平衡二叉搜索树更进了一步。平衡二叉搜索树可能会在插入或者删除时进行不断地左旋或右旋调整,而红黑树可以保证在一个子树中调整时最多发生两次旋转。
注意:这里并不是说红黑树插入或删除调整最多只需要两次旋转,假设一个子树调整为黑高平衡后,如果该子树的根节点为红节点,那么需要继续往上遍历,因为该子树上面一个节点可能也是红节点,具体什么时候会出现这种情况后面会有分析。
红黑树有五条基本性质,其中最后两条是红黑树进行调整的依据。
(1)每个节点是红的或者黑的;
(2)根节点是黑的;
(3)每个叶子节点是黑的;
(4)如果一个节点是红的,则它的两个儿子都是黑的;
(5)堆每个节点,从该节点到其子孙节点的所有路径上包含相同数目的黑节点。
其中(4)的另一层含义就是父子节点不能同时为红节点,(5)的另一层含义是每个子树都需要保持黑高平衡。下面看红黑树的定义。
#define RED 1
#define BLACK 2
typedef int KEY_TYPE;
typedef struct _rbtree_node {
unsigned char color;
struct _rbtree_node *right;
struct _rbtree_node *left;
struct _rbtree_node *parent;
KEY_TYPE key;
void *value;
} rbtree_node;
typedef struct _rbtree {
rbtree_node *root;
rbtree_node *nil;
} rbtree;
二、左旋与右旋
在红黑树中,左旋与右旋的目的是调整黑高,比如一个子树,如果它的右子树的黑节点比左子树的黑节点多,那么就需要左旋,我们可以让这个黑节点旋转到子树的根节点去,这样该子树的左右子树就是黑高平衡的了,右旋同理,我们可以简单地把左旋和右旋理解为红黑树调整黑高地手段,并不是说左旋和右旋只有在红黑树里才有。
下面是左旋与右旋的展示,这里并没有标出颜色,是让大家抛开红黑树,单纯地理解左旋与右旋,此外,这里把指向父节点的线条也画出来了,也是方便大家理解,和后面代码对比时更加清晰。
图1 左旋与右旋
//左旋
void rbtree_left_rotate(rbtree *T, rbtree_node *x) {
//获取X的右节点赋值给y
rbtree_node *y = x->right;
//y的左节点赋值给x的右节点,相当于现在x的右节点是指向原先y的左节点
x->right = y->left;
//y的左节点不为空才有必要将y的左节点的父节点回指
//不然它就是一个叶子节点,值为null,颜色为黑色
if (y->left != T->nil) {
//现在y的左节点的父节点是x
y->left->parent = x;
}
//x的父节点赋值给y的父节点,现在y指向原先x的父节点
y->parent = x->parent;
//x的父节点为空说明x自身就是根节点
if (x->parent == T->nil) {
//因为x为根节点,所以y成为新的根节点
T->root = y;
//前面一步是y指向父节点,这一步是y指向的父节点回指
} else if (x == x->parent->left) {
//原先x是父节点的左节点,那么现在y也是此父节点的左节点
x->parent->left = y;
} else {
原先x是父节点的右节点,那么现在y也是此父节点的右节点
x->parent->right = y;
}
//现在y的左节点指向x
y->left = x;
//x的父节点指向y
x->parent = y;
}
//右旋 右旋与左旋做的操作是对称的,理解了左旋,右旋也不在话下
void rbtree_right_rotate(rbtree *T, rbtree_node *y) {
rbtree_node *x = y->left;
y->left = x->right;
if (x->right != T->nil) {
x->right->parent = y;
}
x->parent = y->parent;
if (y->parent == T->nil) {
T->root = x;
} else if (y == y->parent->right) {
y->parent->right = x;
} else {
y->parent->left = x;
}
x->right = y;
y->parent = x;
}
三、红黑树的插入与调整
红黑树的插入与二叉树的插入是一样的,从根节点遍历,找到合适的位置插入,这个插入的位置一定是叶子节点。下面我们通过代码一步一步地分析插入地过程。
红黑树的插入
//红黑树的插入
void rbtree_insert(rbtree *T, rbtree_node *z) {
//创建一个空节点y,做为临时节点用来遍历
rbtree_node *y = T->nil;
//x做为当前的红黑树的根节点
rbtree_node *x = T->root;
//只要x不为空,继续向下遍历找到待插入节点z的合适位置
//如果x为空,说明红黑树是空树,不需要遍历
while (x != T->nil) {
//y用来保存当前的x,而x继续向下遍历
//当x为空时,那么此时的y就是z的父节点,
y = x;
//z的值小于x的值,说明z应该插入到x的左子树
if (z->key < x->key) {
//x从左子树往下遍历
x = x->left;
//z的值大于x的值,说明z应该插入到x的右子树
} else if (z->key > x->key) {
//x从右子树往下遍历
x = x->right;
} else {
//这里说明x的值和z的值相同,这时候是否插入看业务需求
//如果需要插入,一个可行的方案是将z的值调大或调小一点然后继续对比
//这里是不插入此节点,直接返回
return ;
}
}
//已找到y节点,将z节点的父节点指向y
z->parent = y;
//如果y为空节点,说明是空树,z做为红黑树的根节点
if (y == T->nil) {
T->root = z;
//z的值小于y的值,说明z应该做为y的左节点
} else if (z->key < y->key) {
y->left = z;
//z的值大于y的值,说明z应该做为y的右节点
//这里不会出现z的值和y的值相等的情况,因为在前面的循环中已经进行了处理
} else {
y->right = z;
}
//z的左节点置为空节点
z->left = T->nil;
//z的右节点置为空节点
z->right = T->nil;
//z的节点颜色置为红色,因为插入红节点并不会影响黑高,可以减少调整黑高的概率
//不过会出现z的父节点即y也是红节点的情况,这时候就要执行下面的修复逻辑了
z->color = RED;
//进行插入修复
rbtree_insert_fixup(T, z);
}
下面我们进行分析红黑树的插入调整,只有当插入的节点和插入的节点的父节点都为红节点时才需要调整,我先归纳有哪些情况需要调整,再根据代码分析如何调整以及为什么这样调整。
红黑树的调整
1.插入节点的父节点是插入节点的祖父节点的左节点
(1)插入节点的祖父节点的右节点即叔父节点为红色,如下图2所示
图2
(2)插入节点的祖父节点的右节点即叔父节点为黑色,且插入节点是父节点的左节点,如下图3所示。
图3
(3)插入节点的祖父节点的右节点即叔父节点为黑色,且插入节点是父节点的右节点,如下图4所示。
图4
2.插入节点的父节点是插入节点的祖父节点的右节点
(1)插入节点的祖父节点的左节点即叔父节点为红色,如下图5所示
图5
(2)插入节点的祖父节点的左节点即叔父节点为黑色,且插入节点是父节点的右节点,如下图6所示。
图6
(3)插入节点的祖父节点的左节点即叔父节点为黑色,且插入节点是父节点的左节点,如下图7所示。
图7
以上看起来有六种情况,其实真正有区别的是三种情况,其他三种是对称的,调整的逻辑是一样的。那么现在我们一个一个分析一下。
第一种情况,插入节点的父节点是插入节点的祖父节点的左节点,且插入节点的祖父节点的右节点即叔父节点为红色。首先明确一个事情,无论是插入还是删除,在插入和删除之前红黑树都是红黑树,是满足那五条性质的,其次,插入并不会破坏第五条性质即破坏黑高,删除会破坏黑高,而插入可能破坏的是第四条性质即父子节点都为红节点。那么现在我们在插入后出现父子节点都为红节点后需要调整的是在不破坏黑高的情况下,恢复第四条性质即保证不出现父子节点都为红节点。现在我们来看图2(这里推荐一个贴图工具snipaste,避免看图时窗口上下经常滚动),我们会发现当插入节点的父节点和和叔父节点都为红色时,我们只要把插入节点的父节点和和叔父节点都改为黑色,并且插入节点的祖父节点改为红色,我们就可以确保满足红黑树的第四条性质了,并且这样调整并不会改变原先的黑高,在这里,原先的黑高为2(加上叶子节点),调整后还是2,如下图8所示。这里还有一个地方需要提一下,这种情况插入节点的祖父节点变为红节点了,那么有没有可能插入节点的祖父节点的父节点也是红节点呢,这是很有可能的,因此,我们可以把插入节点的祖父节点做为插入节点继续进行循环判断与调整,直至根节点。
图8
下面展示这部分调整的代码,有助于理解。
//获取插入节点z的叔父节点,赋值给y
rbtree_node *y = z->parent->parent->right;
//y为红色即叔父节点为红色
if (y->color == RED) {
//插入节点z的父节点改为黑色
z->parent->color = BLACK;
//插入节点z的叔父节点改为黑色
y->color = BLACK;
//插入节点z的祖父节点改为红色
z->parent->parent->color = RED;
//将插入节点z的祖父节点赋值给z,继续循环判断
z = z->parent->parent;
}
第二种情况,插入节点的父节点是插入节点的祖父节点的左节点,且插入节点的祖父节点的右节点即叔父节点为黑色,且插入节点是父节点的左节点。我们看图3,我们会发现此时插入节点的叔父节点是一个空节点,但是它确实是一个黑色节点,这里就和上一种情况大相径庭了,因为我们不管怎么变色都会发现会破坏黑高,而且这里有一个隐藏的东西是插入节点的左右子节点也是空节点,如下图9所示。
图9
通过观察图9我们会发现这时候左边有点重了,因此我们可以考虑从左边借一个节点到右边,然后进行变色不就可以了嘛,所以这种情况的调整方法就是右旋加变色,最后调整为图10所示。
图10
下面展示代码,代码中是先变色再右旋,如果右旋后再进行变色会发现插入节点的祖父节点已经改变了,因此先变色再右旋可以简化代码的书写以及便于理解。
//插入节点z的父节点改为黑色
z->parent->color = BLACK;
//插入节点z的祖父节点改为红色
z->parent->parent->color = RED;
//以祖父节点进行右旋,因为我们是希望祖父节点旋转到右边
rbtree_right_rotate(T, z->parent->parent);
//这时候我们会发现,旋转后子树的根节点还是黑色,因此此情况并不需要继续循环判断和调整
第三种情况,插入节点的父节点是插入节点的祖父节点的左节点,且插入节点的祖父节点的右节点即叔父节点为黑色,且插入节点是父节点的右节点。我们看图4,我们会惊奇地发现和第二种情况极其相似,也是左边有点重,我们肯定也会下意识地想到,是不是也可以通过右旋借一个节点到右边,但是我们会发现,右旋以后插入节点的祖父节点插哪里去呢?这就是和第二种情况的区别,因此这种情况需要多一步操作,需要先转换为第二种情况,那么怎么转呢?很简单,我们只需要把插入节点的父节点下沉,而插入节点上浮即可,因此我们可以以插入节点的父节点进行左旋,就可以转换到第二步了,具体转换的图示如下图11.
图11
如上图所示,我们可以发现以插入节点的父节点进行左旋之前应该将插入节点赋值给插入节点的父节点,确保和第二种情况一致。下面是代码展示。
//如果插入节点是插入节点的父节点的右节点
if (z == z->parent->right) {
//先将插入节点z设置成插入节点的父节点,与上述分析一致
z = z->parent;
//然后以插入节点的父节点进行左旋,这里是因为z已经是赋值为插入节点的父节点
rbtree_left_rotate(T, z);
}
//然后进行和第二种情况一样的操作
z->parent->color = BLACK;
z->parent->parent->color = RED;
rbtree_right_rotate(T, z->parent->parent);
到这里为止,有关于插入后需要进行的调整以及如何调整已经分析完毕了,相信如果认真看完的朋友一定对红黑树的插入有了一定的概念了,下面是完整的代码。
void rbtree_insert_fixup(rbtree *T, rbtree_node *z) {
while (z->parent->color == RED) { //z ---> RED
if (z->parent == z->parent->parent->left) {
rbtree_node *y = z->parent->parent->right;
if (y->color == RED) {
z->parent->color = BLACK;
y->color = BLACK;
z->parent->parent->color = RED;
z = z->parent->parent; //z --> RED
} else {
if (z == z->parent->right) {
z = z->parent;
rbtree_left_rotate(T, z);
}
z->parent->color = BLACK;
z->parent->parent->color = RED;
rbtree_right_rotate(T, z->parent->parent);
}
}else {
rbtree_node *y = z->parent->parent->left;
if (y->color == RED) {
z->parent->color = BLACK;
y->color = BLACK;
z->parent->parent->color = RED;
z = z->parent->parent; //z --> RED
} else {
if (z == z->parent->left) {
z = z->parent;
rbtree_right_rotate(T, z);
}
z->parent->color = BLACK;
z->parent->parent->color = RED;
rbtree_left_rotate(T, z->parent->parent);
}
}
}
//这里将根节点置为黑色,是确保最终根节点一定为黑色
//因为第一种情况可能会不断向上迭代导致根节点变为红色,这是一条兜底的代码
T->root->color = BLACK;
}
四、红黑树的删除与调整
现在我们进入红黑树删除的环节,删除的调整对比插入的调整复杂一些,如果抓不到重点肯定会云里雾里,但是在这里只要跟着我的思路走,肯定可以抓到重点,明白删除后调整有哪些情况以及为什么要这样调整。
红黑树的删除
在分析删除后的调整之前,首先我们要搞清楚红黑树是怎么样删除一个节点的。有一个必须确保的点是删除的节点不能是内节点,即有左右子树的节点(不能是空节点),因为直接删除内节点会加大处理的复杂度,并不可取,因此我们会选择删除至多有一个子节点的节点,那么有两种情况,一种是将要删除的节点本来就是至多有一个子节点,这时候直接确认这个节点是要删除的节点,另一种是将要删除的节点有左右子树节点,那么我们可以选择删除左子树值最大的节点或者右子树中值最小的节点,这里我们采用删除右子树中值最小的节点,即后继节点,但是需要注意的是一个节点后继节点不单单是节点的右子树的最小值,当右子树为空时也可能是节点的祖先节点,不过我们这里不会出现后面一种情况,因为既然右子树已经为空为什么还要继续向上遍历祖先节点呢?直接删除该节点不好嘛?下面我们先上代码加深这些逻辑的印象。
//不断向下遍历x的左节点直至为空节点
rbtree_node *rbtree_mini(rbtree *T, rbtree_node *x) {
while (x->left != T->nil) {
x = x->left;
}
return x;
}
//找到一个节点的后继节点
rbtree_node *rbtree_successor(rbtree *T, rbtree_node *x) {
//将节点x的父节点赋值给y
rbtree_node *y = x->parent;
//如果x的右节点不为空,找到右子树的最小值
if (x->right != T->nil) {
//rbtree_mini这个函数是寻找右子树最小值的
return rbtree_mini(T, x->right);
}
//这段代码实际上在我们这里并不会执行,只有前面if语句不成立时才会执行这里
//但是如果前面if语句不成立的话压根就不会找后继节点
//之所以会把这个执行代码放在这里,是因为这个函数就是找一个节点的后继节点的
//保证函数职责的完整性,万一其他地方要用这个函数呢
while ((y != T->nil) && (x == y->right)) {
x = y;
y = y->parent;
}
return y;
}
//创建一个空节点y,用来存放真实删除的节点
rbtree_node *y = T->nil;
//创建一个空节点x,用来存放y的子树
rbtree_node *x = T->nil;
//如果待删除节点z的左子树或者右子树为空或者都为空,那么z被认定为真实删除的节点y
if ((z->left == T->nil) || (z->right == T->nil)) {
y = z;
} else {
//否则就要找到待删除节点z的后继节点
y = rbtree_successor(T, z);
}
前面我们已经知道了怎么确定一个真实要删除的节点了,后面我们就要执行真正要删除的逻辑了。下面我们通过代码来详细描述一下。
//这里的y是真实要删除的节点,x是用来存储y的子树的
//y的左子树不为空节点就把y的左子树赋值给x
if (y->left != T->nil) {
x = y->left;
//y的右子树不为空节点就把y的右子树赋值给x
} else if (y->right != T->nil) {
x = y->right;
}
//如果都为空,那么x就是空节点不变即可
//首先让x指向y的父节点
x->parent = y->parent;
//如果y的父节点为空,说明y是根节点,那么x直接成为新的根节点
if (y->parent == T->nil) {
T->root = x;
//如果y是y的父节点的左节点,那么x也是y的父节点的左节点
//这里相当于y的父节点回指向x
} else if (y == y->parent->left) {
y->parent->left = x;
//如果y是y的父节点的右节点,那么x也是y的父节点的右节点
} else {
y->parent->right = x;
}
//注意,上面执行完以后我们会发现x与y的父节点实现了互指,但是y并没有完全脱离红黑树
//y仍然指向原先的父节点,y也仍然指向原先的子树
//这个是需要考虑的内容,不过这个并不影响我们的测试
//如果y不等于z,即真实删除的节点不等于待删除的节点
//那么我们就需要把y的键和值赋值回给z
//我们之所以要找后继节点,也是确保这里赋值回去以后红黑树仍然是有序的
if (y != z) {
z->key = y->key;
z->value = y->value;
}
//如果删除的节点是黑节点,那么我们就需要进行删除后调整了
if (y->color == BLACK) {
//进行删除后修复,注意传入的参数是x
rbtree_delete_fixup(T, x);
}
红黑树的调整
1.删除节点是删除节点的父节点的左节点
(1)删除节点的兄弟节点是黑色,且兄弟节点的左右节点都是黑色
如图12所示
图12
(2)删除节点的兄弟节点是红色
如图13所示
图13
(3)删除节点的兄弟节点是黑色,且兄弟节点的左右节点都是红色或者左节点是黑色和右节点是红色
如图14所示。
图14
(4)删除节点的兄弟节点是黑色,且兄弟节点的左节点是红色和右节点是黑色
如图15、16所示。
图15
图16
2.删除节点是删除节点的父节点的右节点
(1)删除节点的兄弟节点是黑色,且兄弟节点的左右节点都是黑色
(2)删除节点的兄弟节点是红色
(3)删除节点的兄弟节点是黑色,且兄弟节点的左右节点都是红色或者左节点是红色和右节点是黑色
(4)删除节点的兄弟节点是黑色,且兄弟节点的左节点是黑色和右节点是红色
前面已经总结了删除节点后可能会遇到的各种情况,不过需要提一下的是,一般公认的就是前面四种情况,但是删除节点后还会遇到一种特殊的情况并没有列出来,这种情况调整起来比较简单,我就在这里先说一下。
这种情况就是删除的节点确实是黑色节点,但是它的子树却是红节点,如图17所示。
图17
这种情况调整的话只需要把x节点变为黑色即可,如图18所示,可能也是因为这种情况调整实属简单所以被大部分人忽略了,不过我还是觉得要悟透红黑树,任何蛛丝马迹都不能放过。
图18
言归正传,先声明一点,前面四种情况的顺序是我特地这样安排的,因为这样安排可以让我们的思路是循序渐进的。
第一种情况,删除节点的兄弟节点是黑色,且兄弟节点的左右节点都是黑色。这是这四种情况中最简单的情况,只需要把删除节点的兄弟节点改为红色,并且继续向上迭代即可,如图19所示,注意,这里之所以要继续向上迭代,是因为我们删除了一个黑色节点又把它的兄弟节点改为红色,导致该子树虽然维持了黑高平衡,但是总体黑高却降低了1,那么从整个红黑树看必然有一边的子树的黑高是降低了1的,除非这个子树就是整个红黑树。
图19
//w节点是删除节点x的兄弟节点
rbtree_node *w= x->parent->right;
//删除节点x的兄弟节点w的左右节点都是黑色
if ((w->left->color == BLACK) && (w->right->color == BLACK)) {
//删除节点x的兄弟节点w改为红色
w->color = RED;
//将删除节点x的父节点做为删除节点x继续去循环迭代,确保整颗红黑树都是黑高平衡的
x = x->parent;
}
第二种情况,删除节点的兄弟节点是红色。我们看图13,我们会发现左边的黑高比右边的黑高少,这时候我们会考虑从右边借一个黑节点过来,然后加上变色,是否能够维持删除前的左右黑高呢?如图20所示。
图20
这里我们将兄弟节点变为黑色的目的是在旋转后保持原先的黑高不变,将父节点变为红色的目的是跳出调整的循环,因为我们进行后续的调整后已经维持了和删除节点前的黑高了,如果不跳出循环的话会越调越乱。颜色调整完毕后进行左旋,发现并没有保持黑高平衡,我们仔细观察会发现,这时候有一个子树的情况和第一种情况是一样的,如图21中红框所示。
图21
因此我们只需要进行和第一种情况一样的操作即可,因为此时删除节点的父节点是红色所以会跳出循环,在最后我们再对删除节点的父节点变黑处理,如图22所示。
图22
我们再看代码进行分析。
//当节点不是根节点且颜色是黑色时进入循环
while ((x != T->root) && (x->color == BLACK)) {
//w是删除节点的兄弟节点
rbtree_node *w= x->parent->right;
//如果兄弟节点是红色进行下面的处理
if (w->color == RED) {
//将兄弟节点改为黑色,从而旋转后保持黑高不变
w->color = BLACK;
//父节点改为红色,从而跳出循环
x->parent->color = RED;
//以父节点进行左旋
rbtree_left_rotate(T, x->parent);
//更新旋转后的兄弟节点,继续往下执行
w = x->parent->right;
}
//兄弟节点的左右节点都是黑色
if ((w->left->color == BLACK) && (w->right->color == BLACK)) {
//兄弟节点改为红色
w->color = RED;
//因为降低了黑高,继续向上迭代,不过第二种情况是例外,并不需要向上迭代
//因此父节点改为红色,退出循环
x = x->parent;
}
......
}
//退出循环后,将删除节点x置为黑色
//这条语句有两种情况会发挥作用,一个是前面说的第五种情况,一个就是现在第二种情况
x->color = BLACK;
第三种情况,删除节点的兄弟节点是黑色,且兄弟节点的左右节点都是红色或者左节点是黑色和右节点是红色。我们看图14,我们会发现和第二种情况类似,左边黑高比右边黑高少,我们可以考虑左旋和变色,结果如图23所示。
图23
上图左旋之前还进行了变色,那么进行了哪些变色呢,又为什么要进行这样的变色呢?我们通过代码进行分析。
//将现在父节点的颜色保留给兄弟节点,这个兄弟节点将会通过左旋变成新的该子树的根节点
//这样做的目的是确保该子树和整体的红黑树对接保持不变,我们可以这样理解
//假如原先该子树的根节点是红色,如果现在左旋以后变成了黑色,那么
//虽然该子树的黑高仍然平衡,但是整体红黑树在这个子树方向却黑高加1了
w->color = x->parent->color;
//父节点改为黑色,确保左旋以后该子树的左子树黑高增加
x->parent->color = BLACK;
//兄弟节点的右节点改为黑色,确保右子树旋转之后黑高保持不变
w->right->color = BLACK;
//以父节点进行左旋
rbtree_left_rotate(T, x->parent);
//因为通过上述的调整之后,该子树的黑高和删除节点之前保持不变
//可以确定不再需要进行迭代循环了,所以将x置为根节点
x = T->root;
说到这里,可能会有朋友疑惑,为什么兄弟节点的左右节点都是红色和兄弟节点的左节点是黑色并右节点是红色属于同一种情况呢,我可以先在宏观上给大家进行分析,兄弟节点的右节点如果是红色,那么意味着我们就可以通过左旋进行借位,只要我们左旋以后将兄弟节点的右节点置为黑,同样可以保持右子树的黑高不变,因此,当兄弟节点的右节点为红色时其实就是同一种情况。另外要说明的是,兄弟节点的左节点为黑色而右节点为红色这种情况和第四种情况一样,也是在第一种情况的基础上往上迭代时才能遇到的。下面我通过几个图例给大家展示一下这种情况。
图24
图25
第四种情况,删除节点的兄弟节点是黑色,且兄弟节点的左节点是红色和右节点是黑色。我们看图15,确认的一点是我们在第一种情况的基础上向上迭代时会遇到我们的第四种情况,其实第四种情况最终也会转换为第三种情况,转换方法如图26所示,我们通过代码来阐述是如何转换到第三种情况的。
图26
//如果兄弟节点的右节点为黑色,那么需要进行下面的处理
if (w->right->color == BLACK) {
//兄弟节点的左节点置为黑,这样进行右旋后可以保持和第三种情况一样
//右旋后相当于此节点是第三种情况的兄弟节点
w->left->color = BLACK;
//兄弟节点置为红色,这样右旋后就相当于第三种情况的兄弟节点的右节点
//兄弟节点的右节点是红色就可以通过左旋加置黑保持右子树黑高不变了
w->color = RED;
//以兄弟节点进行右旋,调整成第三种情况
rbtree_right_rotate(T, w);
//重新计算当前的兄弟节点
w = x->parent->right;
}
//下面的逻辑和第三种情况是一样的
w->color = x->parent->color;
x->parent->color = BLACK;
w->right->color = BLACK;
rbtree_left_rotate(T, x->parent);
x = T->root;
到这里为止,红黑树的删除就分析完毕了,另一半的情况是对称的,理解了这一半,另一半也不在话下。红黑树的删除比插入实在是复杂的多,不过只要你跟着我上面的步骤走,我相信你理解红黑树的删除肯定是没有问题的。
五、总结
经过将近一天的鏖战,这篇万字红黑树文章终于出炉,如果你是从头到尾读完的,我相信你即使手撕不出红黑树,也离手撕红黑树不远了。希望在将来面试时红黑树成为你的护身法宝。当然,我实力浅薄,难免有些错误,希望不吝赐教,如果有误导深感抱歉。完整代码在下方。
#define RED 1
#define BLACK 2
typedef int KEY_TYPE;
typedef struct _rbtree_node {
unsigned char color;
struct _rbtree_node *right;
struct _rbtree_node *left;
struct _rbtree_node *parent;
KEY_TYPE key;
void *value;
} rbtree_node;
typedef struct _rbtree {
rbtree_node *root;
rbtree_node *nil;
} rbtree;
rbtree_node *rbtree_mini(rbtree *T, rbtree_node *x) {
while (x->left != T->nil) {
x = x->left;
}
return x;
}
rbtree_node *rbtree_maxi(rbtree *T, rbtree_node *x) {
while (x->right != T->nil) {
x = x->right;
}
return x;
}
rbtree_node *rbtree_successor(rbtree *T, rbtree_node *x) {
rbtree_node *y = x->parent;
if (x->right != T->nil) {
return rbtree_mini(T, x->right);
}
while ((y != T->nil) && (x == y->right)) {
x = y;
y = y->parent;
}
return y;
}
void rbtree_left_rotate(rbtree *T, rbtree_node *x) {
rbtree_node *y = x->right;
x->right = y->left;
if (y->left != T->nil) {
y->left->parent = x;
}
y->parent = x->parent;
if (x->parent == T->nil) {
T->root = y;
} else if (x == x->parent->left) {
x->parent->left = y;
} else {
x->parent->right = y;
}
y->left = x;
x->parent = y;
}
void rbtree_right_rotate(rbtree *T, rbtree_node *y) {
rbtree_node *x = y->left;
y->left = x->right;
if (x->right != T->nil) {
x->right->parent = y;
}
x->parent = y->parent;
if (y->parent == T->nil) {
T->root = x;
} else if (y == y->parent->right) {
y->parent->right = x;
} else {
y->parent->left = x;
}
x->right = y;
y->parent = x;
}
void rbtree_insert_fixup(rbtree *T, rbtree_node *z) {
while (z->parent->color == RED) { //z ---> RED
if (z->parent == z->parent->parent->left) {
rbtree_node *y = z->parent->parent->right;
if (y->color == RED) {
z->parent->color = BLACK;
y->color = BLACK;
z->parent->parent->color = RED;
z = z->parent->parent; //z --> RED
} else {
if (z == z->parent->right) {
z = z->parent;
rbtree_left_rotate(T, z);
}
z->parent->color = BLACK;
z->parent->parent->color = RED;
rbtree_right_rotate(T, z->parent->parent);
}
}else {
rbtree_node *y = z->parent->parent->left;
if (y->color == RED) {
z->parent->color = BLACK;
y->color = BLACK;
z->parent->parent->color = RED;
z = z->parent->parent; //z --> RED
} else {
if (z == z->parent->left) {
z = z->parent;
rbtree_right_rotate(T, z);
}
z->parent->color = BLACK;
z->parent->parent->color = RED;
rbtree_left_rotate(T, z->parent->parent);
}
}
}
T->root->color = BLACK;
}
void rbtree_insert(rbtree *T, rbtree_node *z) {
rbtree_node *y = T->nil;
rbtree_node *x = T->root;
while (x != T->nil) {
y = x;
if (z->key < x->key) {
x = x->left;
} else if (z->key > x->key) {
x = x->right;
} else { //Exist
return ;
}
}
z->parent = y;
if (y == T->nil) {
T->root = z;
} else if (z->key < y->key) {
y->left = z;
} else {
y->right = z;
}
z->left = T->nil;
z->right = T->nil;
z->color = RED;
rbtree_insert_fixup(T, z);
}
void rbtree_delete_fixup(rbtree *T, rbtree_node *x) {
while ((x != T->root) && (x->color == BLACK)) {
if (x == x->parent->left) {
rbtree_node *w= x->parent->right;
if (w->color == RED) {
w->color = BLACK;
x->parent->color = RED;
rbtree_left_rotate(T, x->parent);
w = x->parent->right;
}
if ((w->left->color == BLACK) && (w->right->color == BLACK)) {
w->color = RED;
x = x->parent;
} else {
if (w->right->color == BLACK) {
w->left->color = BLACK;
w->color = RED;
rbtree_right_rotate(T, w);
w = x->parent->right;
}
w->color = x->parent->color;
x->parent->color = BLACK;
w->right->color = BLACK;
rbtree_left_rotate(T, x->parent);
x = T->root;
}
} else {
rbtree_node *w = x->parent->left;
if (w->color == RED) {
w->color = BLACK;
x->parent->color = RED;
rbtree_right_rotate(T, x->parent);
w = x->parent->left;
}
if ((w->left->color == BLACK) && (w->right->color == BLACK)) {
w->color = RED;
x = x->parent;
} else {
if (w->left->color == BLACK) {
w->right->color = BLACK;
w->color = RED;
rbtree_left_rotate(T, w);
w = x->parent->left;
}
w->color = x->parent->color;
x->parent->color = BLACK;
w->left->color = BLACK;
rbtree_right_rotate(T, x->parent);
x = T->root;
}
}
}
x->color = BLACK;
}
rbtree_node *rbtree_delete(rbtree *T, rbtree_node *z) {
rbtree_node *y = T->nil;
rbtree_node *x = T->nil;
if ((z->left == T->nil) || (z->right == T->nil)) {
y = z;
} else {
y = rbtree_successor(T, z);
}
if (y->left != T->nil) {
x = y->left;
} else if (y->right != T->nil) {
x = y->right;
}
x->parent = y->parent;
if (y->parent == T->nil) {
T->root = x;
} else if (y == y->parent->left) {
y->parent->left = x;
} else {
y->parent->right = x;
}
if (y != z) {
z->key = y->key;
z->value = y->value;
}
if (y->color == BLACK) {
rbtree_delete_fixup(T, x);
}
return y;
}
rbtree_node *rbtree_search(rbtree *T, KEY_TYPE key) {
rbtree_node *node = T->root;
while (node != T->nil) {
if (key < node->key) {
node = node->left;
} else if (key > node->key) {
node = node->right;
} else {
return node;
}
}
return T->nil;
}
void rbtree_traversal(rbtree *T, rbtree_node *node) {
if (node != T->nil) {
rbtree_traversal(T, node->left);
printf("key:%d, color:%d\n", node->key, node->color);
rbtree_traversal(T, node->right);
}
}
int main() {
int keyArray[20] = {24,25,13,35,23, 26,67,47,38,98, 20,19,17,49,12, 21,9,18,14,15};
rbtree *T = (rbtree *)malloc(sizeof(rbtree));
if (T == NULL) {
printf("malloc failed\n");
return -1;
}
T->nil = (rbtree_node*)malloc(sizeof(rbtree_node));
T->nil->color = BLACK;
T->root = T->nil;
rbtree_node *node = T->nil;
int i = 0;
for (i = 0;i < 20;i ++) {
node = (rbtree_node*)malloc(sizeof(rbtree_node));
node->key = keyArray[i];
node->value = NULL;
rbtree_insert(T, node);
}
rbtree_traversal(T, T->root);
printf("----------------------------------------\n");
for (i = 0;i < 20;i ++) {
rbtree_node *node = rbtree_search(T, keyArray[i]);
rbtree_node *cur = rbtree_delete(T, node);
free(cur);
rbtree_traversal(T, T->root);
printf("----------------------------------------\n");
}
}