上一篇文章主要讲到了红黑树的基本性质以及插入节点的操做,有了上面的基础后,今天就把红黑树剩余的一个难点也就是删除节点的操做详细的讲一下。算法
红黑树节点的删除方法一开始的操做和二叉搜索树差很少,都是首先判断须要删除的节点,分为三种状况,一是若是这个节点没有子女的话,那么直接修改父节点的值,断开他们之间的关系便可。若是这个节点只有一个子女,那么修改它的父节点,使父节点直接链接其子节点。若是该节点有两个子女的话,那么先删去这个节点的后继,而后把而后把后继的值替换这个节点的值便可。函数
在具体的代码实现中要注意略有不一样,首先没有了NULL的断定,取而代之的是对Nil节点的断定,其次,咱们知道,对红黑树节点的删除颇有可能打破以前讲到的各类红黑树性质的约束,假如删除的是红色的节点的话,那么不须要作任何的修改,但假如删除的是黑色的节点呢?这样就违反了红黑树的性质5,致使一些节点的黑高度下降,这时候就须要调用一些修正方法来处理这种状况了。spa
在删除的过程当中,若是被删除的节点y是黑色的,那么将会致使三个问题。首先,若是y原来是根节点,而y的一个红色孩子就成了新的根,这样违反性质2,其次,若是y的父节点和y的一个非空子节点x都是红的,那么就违反了性质4。第三,删除y将会致使任何包含y的路径的黑节点的个数减小1,这样就破坏了性质5..net
补救的方法就是把节点x视为还有额外的一重黑色,也就是说,若是若是将任意包含x节点的路径的黑节点个数增长1(其实指的就是把本来y的黑色放在x上)。这种假设下,可使可使性质5成立,但却不知足性质1,由于x多了一种颜色。节点x多是双重黑色的或者是红黑的,但注意,这也只是逻辑上有两种颜色,在实际上,它的颜色域仍是只有红色或者黑色一种颜色。这时候须要用到修正函数。指针
修正的函数RB_Delete_Fixup能够恢复性质一、二、4,首先,while循环的目的是使黑色沿树上移,直到:code
1.x指向红黑节点,此时在最后一行将x单独置为黑色blog
2.x指向根节点,这时能够简单的消除那个额外的黑色,或者递归
3.作必要的旋转和颜色修改get
在while循环中,x老是指向具备双重黑色的那个非根节点,首先须要判断x是父节点的左孩子仍是右孩子,用指针w记录x的兄弟节点,由于x是双重黑色的,因此w不多是叶子节点,不然的话,从父节点经过x和经过w的黑高度的不一样了。io
算法中会有四种状况以下图:
下面就这四种状况做出详细说明
状况1:x的兄弟w是红色的。
此时w必需要有黑色的孩子,咱们能够改变w和x的父节点的颜色,再对x的父节点作一次左旋。如今,x的新的兄弟是旋转以前w的某个孩子,其颜色为黑色。这样,我么就把状况1转到了状况二、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;
}