上一篇文章主要講到了紅黑樹的基本性質以及插入節點的操做,有了上面的基礎後,今天就把紅黑樹剩餘的一個難點也就是刪除節點的操做詳細的講一下。算法
紅黑樹節點的刪除方法一開始的操做和二叉搜索樹差很少,都是首先判斷須要刪除的節點,分爲三種狀況,一是若是這個節點沒有子女的話,那麼直接修改父節點的值,斷開他們之間的關係便可。若是這個節點只有一個子女,那麼修改它的父節點,使父節點直接鏈接其子節點。若是該節點有兩個子女的話,那麼先刪去這個節點的後繼,而後把而後把後繼的值替換這個節點的值便可。函數
在具體的代碼實現中要注意略有不一樣,首先沒有了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;
}