红黑树删除的基本步骤:
红黑树的删除与插入一样,需要花费时间O(lg n)。但比插入操作要复杂些。
在红黑树的删除z时,其基本思路是:
1.如果z没有左孩子,则用z右孩子替换z;
2.如果z没有右孩子,则用z左孩子替换z;
3.z的左右子树均不为空,则选出z右子树中最小后代(最左节点)。用该节点替换z。
最后,再对该树进行调整。
情况1和2:
当删除节点a只有一个孩子节点时,直接用孩子节点来替换它。替换后的图中少了一个黑色节点a,但这种情况很容易修复,在delete-fixup中只需要把b节点涂黑就行了。
左边是删除前的图
当删除节点a没有孩子时,节点可能是根节点或孩子都是叶子节点。
其实和上面a只有一个孩子是一个道理,删除节点也是被孩子节点替换了,但它的孩子节点是哨兵节点。
左边是删除前的图
(下面这种情况需要对树进行调整保证红黑性)
情况3:节点有左右孩子
删除节点为a
图中最左是删除前的图,最右是删除后的图,中间是一个操作过程。
图中颜色只是一个代表,不表示红或黑。
删除时用a的右子树的最小后代d来替换a,并且将d的颜色改为a的颜色。这样的话,对于整棵红黑树来说,它的搜索树的性质仍然保持者。而d节点移动到a的位置后,由于它没有左子树,所以直接用它的右孩子f来替代掉它。这时候,对于根节点到叶节点经过f节点所在的路径上,路径的高度可能减少了1(当d节点本来是黑色时),这时需要从f节点开始进行调整。
删除过程伪代码:
下面是删除的伪代码,摘自算法导论。
对于删除中,我们需要使用一个函数transplant用来进行节点的替换操作,所以先将它放在前面。
替换过程:
在该过程中,newnode替换oldnode后,它只更新了与父节点的联系。而newnode和oldnode的孩子都没被改变。所以,要正确的使用newnode替换oldnode,需要保证在调用该函数前或后正确更新newnode的孩子。
该函数不改变节点颜色。
transplant(T, oldnode, newnode) //使用newnode替换oldnode
if oldnode.p == T.nil
T.root = v
elif oldnode == oldnode.p.left
oldnode.p.left = newnode
else
oldnode.p.right = newnode
newnode.p = oldnode.p
删除操作最后调用了一个delete-fixup函数,该函数对树进行调整来保证树的红黑性。
在函数中要注意当z的后继节点y就是它的右孩子时,不能用y的右孩子来替换掉y。因为如果y.p = z时,我们直接调用了transplant(T, y, y.right),那么y.right,即T.nil节点父指针指向了z,而z是我们删除的节点,那么我们不能保证将y节点替换z节点后T.nil父指针指向了正确位置y,在delete-fixup中则会出错。
rbt-delete(T, z)伪代码
rbt-delete(T, z)
fixNode = z
y-org-color = y.color
if z.left == T.nil
fixNode = z.right
rbt-transplant(T, z, z.right);
elif z.right == T.nil
x = z.left
rbt-transplant(T, z, z.left)
else //节点左右孩子都存在的情况
y = tree-minimum(z.right)
y-org-color = y.color
fixNode = y.right
if y.p == z //注意
x.p = y
else
rbt-transplant(T, y, y.right)
y.right = z.right
y.right.p = y
//内层if语句结束
rbt-transplant(T, z, y)
y.left = z.left
y.left.p = y
y.color = z.color
//根据y-org-color颜色判断是否树需要进行调整
if y-org-color == black
delete-fixup(T, fixNode)
delete-fixup伪代码
调整过程中有4种情况
case 1: fixNode节点兄弟节点为红色,调整后进入case2或case 3
case 2: fixNode节点兄弟节点为黑色,且兄弟节点左右孩子都是黑色,case2与case 3\4互斥
case 3: fixNode节点兄弟节点至少有一个是红色,且兄弟节点左节点为红色,调整后进入 case4
case 4: fixNode节点兄弟节点的右孩子为红色
在fixup函数的循环对fixNode为左孩子和fixNode为右孩子要分别操作。
delete-fixup(T, fixNode)
//循环条件:fixNode不为树根且fixNode是黑色
while fixNode != T.nil && fixNode .color == BLACK
//fixNode为左孩子
if fixNode == fixNode .p.left
brother = fixNode .p.right
if brother.color == RED // case 1
brother = BLACK // case 1
fixNode.p.color = RED // case 1
left-rotate(fixNode.p) // case 1
brother = fixNode.p.right // case 1
if brother.left.color == BLACK && brother.right.color == BLACK //case 2
brother.color = RED //case 2
fixNode = fixNode.p //case 2
else //case3 或 case 4
//下面的if是在else语句内,不是与else配套的
if brother.right.color == BLACK //case 3
brother.left.color = BLACK //case 3
brother.color = RED //case 3
right-rotate(brother) //case 3
brother = fixNode.p.right //case 3
//if结束,case 3转换成了case 4
brother.color = fixNode.p.color //case 4
fixNode.color = BLACK //case 4
brother.right.color = BLACK //case 4
left-rotate(fixNode.p) //case 4
fixNode = T.root //case 4
//fixNode为右孩子
else
//情况2与情况1中left与right交换就行了,略去不写
//下面是while循环外的一条语句,也是函数中最后一条语句
fixNode.color = BLACK
伪代码代码分析:
删除操作:
1. 当删除节点少于两个子节点时,我们可以用该节点的一个子节点直接替换掉删除节点(即使节点没有孩子,那么是用叶子节点来替换它)。2. 当删除节点左右子树均存在,则可以将它的后继节点(右子树中最小节点)来替换掉它,这样该子树仍然保持了搜索树的性质。而对它后继节点的移动,相当于1的删除。
3. 最后,考虑到删除操作后树的红黑性可能被破坏,我们需要对其进行调整。
4. y-org-color用来保存y改变前的颜色。
y替换z节点时,原来的y又被它的右孩子替代,y的颜色在替换后设置成z的颜色。相当于在y节点的原来所在从根节点到叶节点的路径中,路径上y节点的颜色节点被删除了。所以我们考虑被移走y节点,如果它是红色的话,它的移除不会对红黑树的性质造成任何破坏。但如果是黑色,则会引起红黑性质5的破坏。
调整操作:
调整中分了4种情况,下面来说明各个情况的调整过程:注意:在每种情况调整前,fixNode所在路径的黑节点个数比其它路径上的黑节点个数要少1(因为在删除操作中,只有y节点的颜色是黑色时,才需要调整。)
case 1: fixNode节点兄弟节点为红色
case 1中对fixNode的父节点进行了一次右旋操作,并且为保证旋转后原来fixNode的兄弟节点所在路径上黑高不会变化,我们将原来的兄弟节点c涂成了黑色,而原来的父节点a涂成了红色。
旋转后fixNode所在路径的黑节点个数仍然比它的新兄弟节点路径上的黑色节点个数少1。我们需要进入case 2 或 case 3\4。
case 2: fixNode节点兄弟节点为黑色,且兄弟节点左右孩子都是黑色,case2与case 3\4互斥
在图中蓝色节点表示该节点颜色不确定
调整是将fixNode的兄弟节点涂红,使得兄弟节点路径上黑高减1,与fixNode所在路径上相等,但现在它们的父节点所在路径上的黑高比其它路径少1,所以然后将fixNode指向它的父节点,然后重新开始循环。重新开始循环只发生在父节点是黑色且不是根节点时,因为如果父节点是红色或根节点,退出循环后父节点会被涂黑。从父节点所在路径上的黑高增加了1,保证了与其它路径上黑高相等。这时候树的红黑性以满足。
case 3: fixNode节点兄弟节点至少有一个是红色,且兄弟节点的左孩子节点为红色,调整后进入case4
在图中蓝色节点表示该节点颜色不能确定
case 3是对当fixNode的兄弟节点brother的左孩子为红色,右孩子为黑色时的调整。它将brother节点进行右旋,得到右边的图,即brother的右孩子为红色时的情况(这时brother的左孩子并不确定是红或是黑),这种情况满足case 4
case 4: fixNode节点兄弟节点的右孩子为红色
图中蓝色和紫色表示节点颜色不确定。
调整操作是对fixNode的父节点a进行右旋,并且在旋转前,我们把fixNode的兄弟节点c设置为a的颜色,然后将a的颜色设置成黑色,并且把c的右孩子e涂成红色。
可以看到,经过右旋后的树将fixNode所在路径上的黑高曾加了1。并且保持了其它路径上的黑高不变。所以到这儿树的红黑性质已经被修复,可以退出循环。退出并不是直接的,因为循环结束后会对fixNode的颜色进行一次涂黑操作,这儿将fixNode设置成树的根节点,使得循环条件为假,同时又让循环的fixNode颜色的赋值不会造成任何破坏。
红黑树删除的C代码:
/********************************** 删除 *************************************/
void RBT_delete(RBTree *T, RBTnode_t *node)
{
RBTnode_t *replace = node;
RBTnode_t *fixNode;
color_t replaceOrigColor = replace->color; //保存替代节点颜色值
if(node->left == T->nil) //无左子树
{
fixNode = node->right;
transplant(T, node, node->right);
}
else if(node->right == T->nil) //无右子树
{
fixNode = node->left;
transplant(T, node, node->left);
}
else //左右子树存在
{
replace = Tree_mininum(T, node->right); //左右孩子存在时,用后继节点来替代删除节点
replaceOrigColor = replace->color; //更新保存的替代节点颜色值
fixNode = replace->right;
/* 对于右子树只有一个单独节点 */
if(replace->p == node)
{
/* 赋值是为了保证fixNode是T.nil节点时fixup工作仍能正常进行 */
fixNode->p = replace;
}
else
{
/* 先将replace节点从原位置删除,用其右子树取代它 */
transplant(T, replace, replace->right);
replace->right = node->right;
replace->right->p = replace;
}
transplant(T, node, replace);
replace->left = node->left;
replace->left->p = replace;
replace->color = node->color; /* 设置替代节点为原节点的颜色值 */
}
if(node == T->root)
T->root = replace;
free(node);
if(replaceOrigColor == BLACK) /* 调整树保证其红黑性 */
delete_fixup(T, fixNode);
}
/*************************** 删除操作后对树的调整 ****************************/
void delete_fixup(RBTree *T, RBTnode_t *fixNode)
{
RBTnode_t *brother = NULL;
while(fixNode != T->root && fixNode->color == BLACK)
{
if(fixNode == fixNode->p->left)
{
brother = fixNode->p->right;
/* case 1, 操作后fixNode右兄弟变为黑色 */
if(brother->color == RED) /* case 1 */
{
brother->color = BLACK;
fixNode->p->color = RED;
left_rorate(T, fixNode->p);
brother = fixNode->p->right;
}
/* 下面处理fixNode右兄弟为黑色情况 */
if(brother->left->color == BLACK && brother->right->color == BLACK)
{
brother->color = RED; /* case 2 */
fixNode = fixNode->p;
}
else
{
if(brother->right->color == BLACK) /* case 3 */
{
brother->left->color = BLACK;
brother->color = RED;
right_rotate(T, brother);
brother = fixNode->p->right;
}
brother->color = fixNode->p->color; /* case 4 */
fixNode->p->color = BLACK;
brother->right->color = BLACK;
left_rorate(T, fixNode->p);
fixNode = T->root; /* 使循环条件为假,结束while循环 */
}
}
else
{
brother = fixNode->p->left;
if(brother->color == RED) /* case 1 */
{
brother->color = BLACK;
fixNode->p->color = RED;
right_rotate(T, fixNode->p);
brother = fixNode->p->left;
}
if(brother->left->color == BLACK && brother->right->color == BLACK)
{
brother->color = BLACK; /* case 2 */
fixNode = fixNode->p;
}
else
{
if(brother->left->color == BLACK) /* case 3 */
{
brother->right->color = BLACK;
brother->color = RED;
left_rorate(T, brother);
brother = fixNode->p->left;
}
brother->color = fixNode->p->color; /* case 4 */
fixNode->p->color = BLACK;
brother->left->color = BLACK;
right_rotate(T, fixNode->p);
fixNode = T->root;
}
}
}
fixNode->color = BLACK;
}
/*
* transplant: 使用newnode替代oldnode节点
* 替代后newnode节点与oldnode的父节点连接起来
* newnode 的孩子不会被改变,
* 应在本函数调用前或后保证将oldnode的孩子放置到newnode的孩子节点
* (因为newnode中还得保证其右孩子正确放置,所以不能在本函数中设置孩子指针)
*/
void transplant(RBTree *T, RBTnode_t *oldnode, RBTnode_t *newnode)
{
if(oldnode == T->nil)
T->root = newnode;
else if(oldnode == oldnode->p->left)
oldnode->p->left = newnode;
else
oldnode->p->right = newnode;
newnode->p = oldnode->p;
}
/* 返回node为根的子树最小节点位置 */
RBTnode_t* Tree_mininum(RBTree *T, RBTnode_t *node)
{
RBTnode_t *min = node;
while(min != T->nil && min->left != T->nil)
min = min->left;
return min;
}