上一篇文章主要讲到了红黑树的基本性质以及插入节点的操作,有了上面的基础后,今天就把红黑树剩余的一个难点也就是删除节点的操作详细的讲一下。
红黑树节点的删除方法一开始的操作和二叉搜索树差不多,都是首先判断需要删除的节点,分为三种情况,一是如果这个节点没有子女的话,那么直接修改父节点的值,断开他们之间的关系即可。如果这个节点只有一个子女,那么修改它的父节点,使父节点直接连接其子节点。如果该节点有两个子女的话,那么先删去这个节点的后继,然后把然后把后继的值替换这个节点的值即可。
在具体的代码实现中要注意略有不同,首先没有了NULL的判定,取而代之的是对Nil节点的判定,其次,我们知道,对红黑树节点的删除很有可能打破之前讲到的各种红黑树性质的约束,假如删除的是红色的节点的话,那么不需要做任何的修改,但假如删除的是黑色的节点呢?这样就违反了红黑树的性质5,导致一些节点的黑高度降低,这时候就需要调用一些修正方法来处理这种情况了。
在删除的过程中,如果被删除的节点y是黑色的,那么将会导致三个问题。首先,如果y原来是根节点,而y的一个红色孩子就成了新的根,这样违反性质2,其次,如果y的父节点和y的一个非空子节点x都是红的,那么就违反了性质4。第三,删除y将会导致任何包含y的路径的黑节点的个数减少1,这样就破坏了性质5.
补救的方法就是把节点x视为还有额外的一重黑色,也就是说,如果如果将任意包含x节点的路径的黑节点个数增加1(其实指的就是把原本y的黑色放在x上)。这种假设下,可以使可以使性质5成立,但却不满足性质1,因为x多了一种颜色。节点x可能是双重黑色的或者是红黑的,但注意,这也只是逻辑上有两种颜色,在实际上,它的颜色域还是只有红色或者黑色一种颜色。这时候需要用到修正函数。
修正的函数RB_Delete_Fixup可以恢复性质1、2、4,首先,while循环的目的是使黑色沿树上移,直到:
1.x指向红黑节点,此时在最后一行将x单独置为黑色
2.x指向根节点,这时可以简单的消除那个额外的黑色,或者
3.做必要的旋转和颜色修改
在while循环中,x总是指向具有双重黑色的那个非根节点,首先需要判断x是父节点的左孩子还是右孩子,用指针w记录x的兄弟节点,因为x是双重黑色的,所以w不可能是叶子节点,否则的话,从父节点通过x和通过w的黑高度的不同了。
算法中会有四种情况如下图:
下面就这四种情况作出详细说明
情况1:x的兄弟w是红色的。
此时w必须要有黑色的孩子,我们可以改变w和x的父节点的颜色,再对x的父节点做一次左旋。现在,x的新的兄弟是旋转之前w的某个孩子,其颜色为黑色。这样,我么就把情况1转到了情况2、3或者4。
当节点w为黑色时,根据w的子节点的颜色进行不同情况的区分。
情况2:x的兄弟w是黑色的,且w的两个孩子也是黑色的。
由于w的两个孩子都是黑色的,且w本身也是黑色的,故直接把x去掉一重黑色,并把w换做红色。为了补偿从x中去掉的一重黑色,需要在原来红色或是黑色的x的父节点中新增一重额外黑色。然后把x的父节点作为新的x节点。注意,如果是从情况1进入情况2的,那么x的父节点本来是红色的,现在成为了红黑色。因此,x的新的颜色域填写Red。当循环结束后,节点x被单独置为黑色。
情况3:x的兄弟w是黑色的,且w的左孩子是红色的,右孩子是黑色的。
此时交换w和w的左孩子的颜色,并对w进行右旋,现在x的情况是w是一个有一个红色的黑色节点,这样进入情况4。
情况4:x的兄弟w是黑色的,且w的右孩子是红色的。
通过做某些颜色修改并对x的父节点做一次左旋,去掉x的额外颜色把它变成单独的黑色。将x置为根后,循环结束。
这里把完整的代码贴出来:
#include <stdio.h>
#include <stdlib.h>
typedef enum Color{Red,Black}Color;
/*这里定义树的结构,每个节点为Node结构体,再加上一个头指针Tree*
在红黑树中,多了一个颜色的域Color,这里用枚举表示*/
typedef struct Node
{
int data;
Color color;
struct Node* left;
struct Node* right;
struct Node* parent;
}Node;
Node Nil={0,Black,NULL,NULL,NULL};
typedef struct Tree
{
Node* Root;
}Tree;
/*一下的操作主要针对普通的二叉搜索树进行,但一些搜索、前去后继等操作也是可以直接用的*/
/*对树进行中序遍历*/
void Mid_Traverse(Node* Root)
{
if(Root!=&Nil)
{
Mid_Traverse(Root->left);
printf("%d ",Root->data);
Mid_Traverse(Root->right);
}
}
/*普通二叉树的插入操作,对红黑树不能用这个函数!*/
/*以下函数是对树进行插入操作
定义两个Node变量x和y,一开始x指向根节点,y为空
然后将x的值一次往下递减向左边下降还是右边依据和z的比较,而y的值一直都是x的父节点,以防当x为空时,就找不到这棵树了
然后让z的父节点指向y,相当于把z放到x的地方
当然,需要判断这棵树是否一开始就是空的,如果y是空的话,那么直接把更节点给z
否则的话更具z的值与y比较大小,判断是把z放到左边还是右边*/
void Tree_Insert(Tree* T,Node* z)
{
Node* y=NULL;
Node* x=T->Root;
while(x!=NULL)
{
y=x;
if(z->data<x->data)
x=x->left;
else
x=x->right;
}
z->parent=y;
if(y==NULL)
{
T->Root=z;
}
else
{
if(z->data<y->data)
y->left=z;
else
y->right=z;
}
}
/*查找函数,从根节点进行递归查找,当查找的当前节点为空或者节点就是要找的那个的话,停止查找
否则向下进行查找,向左边还是向右边取决于节点的值与k的比较*/
Node* Tree_Search(Node* Root,int k)
{
if(Root==NULL||k==Root->data)
return Root;
if(k<Root->data)
return Tree_Search(Root->left,k);
else
return Tree_Search(Root->right,k);
}
/*下面两个函数返回树的最小值和最大值,就是一直往左走或者一直往右走就行了*/
Node* Tree_Minimum(Node* Root)
{
while(Root->left!=&Nil)
Root=Root->left;
return Root;
}
Node* Tree_Maximum(Node* Root)
{
while(Root->right!=&Nil)
Root=Root->right;
return Root;
}
/*某一个节点的后继的查找
如果这个节点的右孩子不为空的话,那么只要以右孩子为根节点,返回右子树的最小值就行了
否则的话,就要向上回溯,节点y首先指向x的父节点
只要y不为空(此时到了根节点了,直接拿来就行了),并且x是y的右孩子(说明了x的值还是大于y的。。)的话,就一直向上回溯
两种情况停止循环:一个是到达了根节点了,中序遍历的话此时下一个节点必然是根节点
另一种情况是当x是y的左孩子,那么y的是就是大于x的了,那么x的下一个元素必然是y了*/
Node* Tree_Successor(Node* x)
{
if(x->right!=&Nil)
return Tree_Minimum(x->right);
Node* y=x->parent;
while(y!=&Nil&&x==y->right)
{
x=y;
y=y->parent;
}
return y;
}
/*前驱的查找与上面的分析类似*/
Node* Tree_Predecessor(Node* x)
{
if(x->left!=&Nil)
return Tree_Maximum(x->left);
Node* y=x->parent;
while(y!=&Nil&&x==y->left)
{
x=y;
y=y->parent;
}
return y;
}
/*普通的二叉搜索树的删除操作,红黑树不能用这个! */
/*节点的删除操作,前面几行算法首先确定需要删除的元素y,z有两个孩子的话那么删除z的后继,否则直接删除z
然后将x置为y的非空子女,若果y无子女的话,那么x就设置为空
如果x非空的话,通过修改指针将y删除
否则的话还要考虑边界情况,若果要删除的y是根节点的话,那么直接把根节点给x(注意,x要么为空,要么就是y的唯一一个子树)
如果y是左孩子的话,那么把x放在y的父节点的左孩子位置上,反之放在右孩子上
最后判定,如果y是z的后继的话,就是说删除掉的节点不是z的话,那么要把z的值赋值给y*/
Node* Tree_Delete(Tree* T,Node* z)
{
Node* y;Node* x;
if(z->left==NULL||z->right==NULL)
y=z;
else
y=Tree_Successor(z);
if(y->left!=NULL)
x=y->left;
else
x=y->right;
if(x!=NULL)
x->parent=y->parent;
if(y->parent==NULL)
T->Root=x;
else
{
if(y==y->parent->left)
y->parent->left=x;
else
y->parent->right=x;
}
if(y!=z)
z->data=y->data;
return y;
}
/*以下是对节点x进行左旋左旋操作
先完成Y的左孩子到X的连接,首先用节点Y指向X的右孩子,把Y的左孩子放到X的右孩子处
判断,如果Y的左孩子是不空的话,那么直接把X作为Y的左孩子的父节点
然后完成Y节点和X的父节点的连接。把Y的父节点直接连向X的父节点,当然,如果X的父节点是空的话,那么根节点就是Y
判断两种情况,如果X是左孩子的话,那么那么Y就是左孩子,否则Y是右孩子
最后完成X于Y的连接,把X的父节点为Y,Y的左孩子为X*/
void Left_Rotate(Tree* T,Node* X)
{
Node* Y=X->right;
X->right=Y->left;
if(Y->left!=&Nil)
Y->left->parent=X;
Y->parent=X->parent;
if(X->parent==&Nil)
T->Root=Y;
else if(X->parent->left==X)
X->parent->left=Y;
else
X->parent->right=Y;
Y->left=X;
X->parent=Y;
}
/*右旋操作,和左旋操作完全一样,代码是对称的*/
void Right_Rotate(Tree* T,Node* Y)
{
Node* X=Y->left;
Y->left=X->right;
if(X->right!=&Nil)
X->right->parent=Y;
X->parent=Y->parent;
if(Y->parent==&Nil)
T->Root=X;
else if(Y->parent->left==Y)
Y->parent->left=X;
else
Y->parent->right=X;
X->right=Y;
Y->parent=X;
}
/*以下是对红黑树插入之后的修正操作
下面的循环条件就是按照之前的那三种情形来实现的
首先,判读z的父节点颜色是否为红色的,如果是黑色的话,就能不需要任何修正,但如果是红色的话,就要进行下一步
判断z的父节点是爷爷节点的左孩子还是右孩子,这样就区分为上面曾讲到的情形A和情形B
首先判断的是情形A,B的话与之类似就不讲了。在情形A中,父节点处于左孩子位置上,接下来的一步,就要判断z的大叔节点了
令Y等于z的大叔节点,就是z的爷爷节点的右孩子(情形A),如果大叔节点是红色的话,那么恭喜可以直接重新上色,为情形1
但如果大叔节点不是红色的呢?那么就要判断情形2还是情形3
如果z是右孩子的话,那么就是情形2,此时对z的父节点进行左旋操作,并直接把z指向他的父节点。
然后重新上色,把z的父节点上成黑色,爷爷节点上成红色,然后对爷爷节点进行右旋操作即可
如果z是左孩子的话,直接就是第三种情形,直接右旋即可*/
void RB_Insert_Fixup(Tree* T,Node* z)
{
Node* Y;
while(z->parent->color==Red)
{
if(z->parent==z->parent->parent->left)
{
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;
}
else
{
if(z==z->parent->right)
{
z=z->parent;
Left_Rotate(T,z);
}
z->parent->color=Black;
z->parent->parent->color=Red;
Right_Rotate(T,z->parent->parent);
}
}
else if(z->parent==z->parent->parent->right)
{
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;
}
else
{
if(z==z->parent->left)
{
z=z->parent;
Right_Rotate(T,z);
}
z->parent->color=Black;
z->parent->parent->color=Red;
Left_Rotate(T,z->parent->parent);
}
}
}
T->Root->color=Black;
}
/*红黑树的插入操作,除去最后两行外,其余的和普通的二叉树插入是一样的
最后做了两个工作,1.将插入的节点z的颜色设置成红色2.调用RB_Insert_Fixup函数进行修正*/
void RB_Insert(Tree* T,Node* z)
{
Node* Y=&Nil;
Node* X=T->Root;
while(X!=&Nil)
{
Y=X;
if(z->data<X->data)
X=X->left;
else
X=X->right;
}
z->parent=Y;
if(Y==&Nil)
{
z->color=Black;
T->Root=z;
return;
}
else if(z->data<Y->data)
Y->left=z;
else
Y->right=z;
z->left=&Nil;
z->right=&Nil;
z->color=Red;
RB_Insert_Fixup(T,z);
}
/*传递的节点x有两种情况,在y被删除之前,如果y有个不是哨兵Nil的节点,那么x就是y的唯一的孩子
如果y没有孩子,那么x就是哨兵Nil。但无论x是什么值,x的父节点都是先前y的父节点*/
void RB_Delete_Fixup(Tree* T,Node* x)
{
Node* w;
while(x!=T->Root&&x->color==Black)
{
if(x==x->parent->left)
{
w=x->parent->right;/*进入情况1*/
if(w->color==Red)
{
w->color=Black;
x->parent->color=Red;/*交换颜色后进行左旋操作,新的w为x的新的兄弟节点*/
Left_Rotate(T,x->parent);
w=x->parent->right;
}
if(w->left->color==Black&&w->right->color==Black)/*情况2*/
{
w->color=Red;
x=x->parent;
}
else
{
if(w->right->color==Black)/*右孩子是黑色的,进入情况3*/
{
w->left->color=Black;
w->color=Red;
Right_Rotate(T,w);
w=x->parent->right;
}
else
{
w->color=x->parent->color;/*进入情况4*/
x->parent->color=Black;
w->right->color=Black;
Left_Rotate(T,x->parent);
x=T->Root;
}
}
}
else/*剩下的部分是完全对称的*/
{
w=x->parent->left;
if(w->color==Red)
{
w->color=Black;
x->parent->color=Red;
Right_Rotate(T,x->parent);
w=x->parent->left;
}
if(w->right->color==Black&&w->left->color==Black)
{
w->color=Red;
x=x->parent;
}
else
{
if(w->left->color==Black)
{
w->right->color=Black;
w->color=Red;
Left_Rotate(T,w);
w=x->parent->left;
}
else
{
w->color=x->parent->color;
x->parent->color=Black;
w->right->color=Black;
Right_Rotate(T,x->parent);
x=T->Root;
}
}
}
}
x->color=Black;
}
/*删除红黑树节点的操作,一开始也是和普通二叉树的删除操作基本一样,但也有三点不同
首先,在二叉树中所有的NULL都换做了对Nil节点的引用
其次,不在需要判断x是否为空,直接将x的父节点连接在y上就行了,因为x就算是Nil也是有完整结构的
最后,判断如果删除的y是黑色的,那么调用修正方法*/
Node* RB_Delete(Tree* T,Node* z)
{
Node* y;
Node* x;
if(z->left==&Nil||z->right==&Nil)
y=z;
else
y=Tree_Successor(z);
if(y->left!=&Nil)
x=y->left;
else
x=y->right;
x->parent=y->parent;
if(y->parent==&Nil)
T->Root=x;
else if(y==y->parent->left)
y->parent->left=x;
else
y->parent->right=x;
if(y!=z)
{
z->data=y->data;
}
if(y->color==Black)
RB_Delete_Fixup(T,x);
return y;
}
int main()
{
Tree T;
T.Root=&Nil;
Node N1;N1.data=12;N1.left=N1.right=N1.parent=&Nil;
Node N2;N2.data=5;N2.left=N2.right=N2.parent=&Nil;
Node N3;N3.data=2;N3.left=N3.right=N3.parent=&Nil;
Node N4;N4.data=9;N4.left=N4.right=N4.parent=&Nil;
Node N5;N5.data=18;N5.left=N5.right=N5.parent=&Nil;
Node N6;N6.data=15;N6.left=N6.right=N6.parent=&Nil;
Node N7;N7.data=19;N7.left=N7.right=N7.parent=&Nil;
Node N8;N8.data=17;N8.left=N8.right=N8.parent=&Nil;
//Tree_Insert(&T,&N1);Tree_Insert(&T,&N2);Tree_Insert(&T,&N3);Tree_Insert(&T,&N4);
//Tree_Insert(&T,&N5);Tree_Insert(&T,&N6);Tree_Insert(&T,&N7);Tree_Insert(&T,&N8);
RB_Insert(&T,&N1);
printf("插入节点%d后,根节点为%d\n",N1.data,T.Root->data);
RB_Insert(&T,&N2);
printf("插入节点%d后,根节点为%d\n",N2.data,T.Root->data);
RB_Insert(&T,&N3);
printf("插入节点%d后,根节点为%d\n",N3.data,T.Root->data);
RB_Insert(&T,&N4);
printf("插入节点%d后,根节点为%d\n",N4.data,T.Root->data);
RB_Insert(&T,&N5);
printf("插入节点%d后,根节点为%d\n",N5.data,T.Root->data);
RB_Insert(&T,&N6);
printf("插入节点%d后,根节点为%d\n",N6.data,T.Root->data);
RB_Insert(&T,&N7);
printf("插入节点%d后,根节点为%d\n",N7.data,T.Root->data);
RB_Insert(&T,&N8);
printf("插入节点%d后,根节点为%d\n",N8.data,T.Root->data);
Mid_Traverse(T.Root);
printf("\n");
Node* S=NULL;
S=Tree_Search(T.Root,17);
if(S!=NULL)
printf("查找成功:%d\n",S->data);
else
printf("查找失败\n");
Node* Min,*Max;
Min=Tree_Minimum(T.Root);Max=Tree_Maximum(T.Root);
printf("最小节点的值为:%d\n最大节点的值为:%d\n",Min->data,Max->data);
Node* Su;
Su=Tree_Successor(S);
printf("%d的下一个元素是%d\n",S->data,Su->data);
Su=Tree_Predecessor(S);
printf("%d的上一个元素是%d\n",S->data,Su->data);
printf("删除一个元素:%d\n",S->data);
RB_Delete(&T,S);
Mid_Traverse(T.Root);
printf("\n删除一个元素:%d\n",Su->data);
RB_Delete(&T,Su);
Mid_Traverse(T.Root);
//Tree_Delete(T.Root,S);
//Mid_Traverse(T.Root);
return 0;
}