红黑树删除算法详解
删除算法相较于插入要稍微复杂些。
删除操作,首先与二叉树删除一致,找到删除的节点,若是只有左节点或者只有右节点或者没有左右子树,那么删除的节点就是原来的节点,只要将删除节点的父节点指针指向子节点。
2)若是同时有左右子树,利用查找后继,就是找到右子树中最小的元素,然后将找到节点与这个节点的值进行交换,那么删除的就是这个后继节点,这个后继节点是基于中序遍历找出来的。
3)我们找到可以交换的节点,现在只要删除这个节点就好了,现在要判断这个新的删除节点的颜色。(1)如果这个节点是红的,那么很好,经过它的节点黑色并没有改变,红黑树的几个性质也没有受到变化。(2)、找到待删除节点的孩子节点x,很肯定的是待删除节点只有一个子树。也就是说这个x是确定的,那么现在又要分情况来讲。
4)如果待删除节点是黑色的,x是红色的,很好,只要将x的颜色属性置为黑色就好。现在通过x节点的子树上黑色节点又增加了,回到原来的数目。现在还有一种很糟糕的情况是待删除与x节点的颜色属性都是黑色。问题出现了。通过前面的删除操作,通过x节点路径的黑色节点数目减少一个,现在就要对这棵树进行重新的构造。(记住,这个x节点可能下面还有子树)。现在我们针对这种情况分为4种情形来讨论(总的是根据兄弟节点的颜色)
template<class KEY,class VAL>
bool RB_Tree<KEY,VAL>::Delete(KEY key,VAL val)
{
RB_Node<KEY,VAL> *deleteNode=nullNode;//要删除的节点
RB_Node<KEY,VAL>*delNodeChild=nullNode;//要删除节点的孩子节点
RB_Node<KEY,VAL> *index=root;//index为找到的节点
while(index!=nullNode)
{
if(*val<*(index->val))
{
index=index->leftNode;
}
else if (*val>*(index->val))
{
index=index->rightNode;
}
else
{
break;
}
}
if (index->leftNode==nullNode||index->rightNode==nullNode)//分两种情况,一种是只有左子树或者只有右子树
{
deleteNode=index;
}
else
{
deleteNode=successor(index);//若同时有左右子树,找到后继
//见前面的文章,查找后继节点
}
if(deleteNode->leftNode!=nullNode)
{
delNodeChild=deleteNode->leftNode;
}
else
{
delNodeChild=deleteNode->rightNode;//获知孩子为左子树还是右子树
}
delNodeChild->parentNode=deleteNode->parentNode;//将父节点孩子指针指向这个节点的孩子节点
if (delNodeChild->parentNode==nullNode)
{
root=delNodeChild;
}
else
{
if (deleteNode==deleteNode->parentNode->leftNode)//获取当前节点为父节点的哪个节点
{
deleteNode->parentNode->leftNode=delNodeChild;
}
else
{
deleteNode->parentNode->rightNode=delNodeChild;
}
}
if (deleteNode!=index)
{
index->key=deleteNode->key;//交换值
}
if (deleteNode->color==BLACK)//若删除的节点为黑色
{
DeleteFixup(delNodeChild);//进行树的重新构造
}
}
template<class KEY,class VAL>
bool RB_Tree<KEY,VAL>::DeleteFixup(RB_Node<KEY,VAL>* delNode)
{
RB_Node<KEY,VAL>*w;
while(delNode!=root&&delNode->color==BLACK)
{
if (delNode==delNode->parentNode->leftNode)//当前节点为父节点的左子树
{
/*1、若兄弟节点为红色。将兄弟节点颜色置为黑色,
x节点的父节点置为红色,然后将父节点进行左旋。
此时所做的操作并没有改变什么。通过每个节点的黑色数目依旧没有改变,
我们只是为了进入下面的3种情况。开始的时候我在想为什么不采用与情形2一样的操作,
将兄弟节点也置为红色,那么此时每课树的黑色节点数目都减少一个,然后继续回溯。
但是这个时候我们发现,这样的操作,会使得通过x的黑色节点数目也减少1个,很傻逼的行为*/
w=delNode->parentNode->rightNode;
if (w->color==RED)//1.兄弟节点为红色
{
delNode->parentNode->color==RED;//将父节点置为红色
w->color==BLACK;//兄弟节点为黑色
RotateLeft(delNode->parentNode);
w=delNode->parentNode->rightNode;//旋转
}
else if (w->color==BLACK)
{
if(w->leftNode->color==BLACK&&w->rightNode->color==BLACK)//2.兄弟节点极其孩子节点均为黑色
{
/*2、若兄弟节点为黑色。将兄弟节点颜色置为红色,
那么此时通过每个叶子节点的黑色数目是一致的,
不过未通过x父节点的叶子节点数目反而会比通过的多一个。
于是我们回溯,继续构造。记住,这个时候如果是从1过来的,
也就是说父节点是红色的,此时只要将父节点的颜色置为黑色就ok啦。*/
w->color==RED;
delNode=delNode->parentNode;
}
if (w->color==BLACK&&w->leftNode->color==RED)//3.兄弟节点为黑色,w的左子树为红色
{
/*3、若兄弟节点为黑色,且其左子树的颜色为红色,
将这个左子树颜色置为黑色,w的颜色置为红色,
进行右旋。为什么这么做呢,只是为了进入4的情况。*/
w->color==RED;
w->leftNode->color=BLACK;
RotateRight(w);
w=delNode->parentNode->rightNode;
}
if(w->leftNode->color==RED&&w->rightNode->color==RED)//4.兄弟节点为黑色,孩子节点均为红色
{
/*4、若兄弟节点的左右子树都为红色,且兄弟节点为黑色,
将w的右子树置为黑色,w的颜色置为x的父节点一样的颜色,
这个时候父节点的颜色可以为红色也可以为黑色。然后左旋。*/
w->color==delNode->parentNode->color;
delNode->parentNode->color=BLACK;
w->rightNode->color=BLACK;
RotateLeft(delNode->parentNode);
delNode=root;//
}
}
}
else if (delNode==delNode->parentNode->rightNode)
{
w=delNode->parentNode->leftNode;
if (w->color==RED)
{
delNode->parentNode->color=RED;
w->color=BLACK;
RotateRight(delNode->parentNode);
}
else if (w->color==BLACK)
{
if (w->leftNode->color==BLACK&&w->rightNode->color==BLACK)
{
w->color=RED;
delNode=delNode->parentNode;
}
else if (w->rightNode->color==RED&&w->leftNode->color==BLACK)
{
w->rightNode->color=BLACK;
w->color=RED;
RotateLeft(w);
}
if (w->rightNode==RED&&w->leftNode==RED)
{
w->leftNode=BLACK;
w->color=delNode->color;
delNode->parentNode=BLACK;
RotateRight(delNode->parentNode);
}
}
}
}
delNode->color=BLACK;
return true;
}
最后我们将x的颜色置为黑色
从上面可以知道我们有两种情况会出循环,一种是兄弟节点与父节点都是红色,只要将父节点的颜色置为黑色。
一种是4的情况,左旋使得通过x的节点黑色节点数目增加一个,达到平衡。