這兒主要給出紅黑樹的代碼實現,和我的一些理解。具體的紅黑樹介紹在算法導論的163頁,也可以自己google或百度。
紅黑樹簡介:
紅黑樹是每個節點都帶有顏色屬性的二叉查找樹,顏色或紅色或黑色。在二叉查找樹強制一般要求以外,對於任何有效的紅黑樹我們增加了如下的額外要求:
性質1. 節點是紅色或黑色。
性質2. 根節點是黑色。
性質3 每個葉節點是黑色的。
性質4 每個紅色節點的兩個子節點都是黑色。(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點)
性質5. 從任一節點到其每個葉子的所有路徑都包含相同數目的黑色節點。
這些約束強制了紅黑樹的關鍵性質: 從根到葉子的最長的可能路徑不多於最短的可能路徑的兩倍長。結果是這個樹大致上是平衡的。因為操作比如插入、刪除和查找某個值的最壞情況時間都要求與樹的高度成比例,這個在高度上的理論上限允許紅黑樹在最壞情況下都是高效的,而不同於普通的二叉查找樹。
要知道為什么這些特性確保了這個結果,注意到性質4導致了路徑不能有兩個毗連的紅色節點就足夠了。最短的可能路徑都是黑色節點,最長的可能路徑有交替的紅色和黑色節點。因為根據性質5所有最長的路徑都有相同數目的黑色節點,這就表明了沒有路徑能多於任何其他路徑的兩倍長。 (摘自百度百科)
因為一顆空的紅黑樹就是一顆滿足所有性質的紅黑樹,所以構造出一顆紅黑樹是簡單的。重要的是如何在執行可能會破壞紅黑樹的的操作:插入和刪除。如何紅黑樹繼續保持這些性質。
下面就針對插入和刪除時如何保持紅黑樹的性質進行具體的解釋和實現。
插入和刪除都要用到的基本操作是旋轉。(旋轉是個簡單而又經典的操作!)
插入:
插入的基本操作和二叉查找樹的插入操作類似(如果不是很清楚可以到算法導論的151頁學習一下,或者到我的上一篇博客看看代碼),再對每個新插入的節點的color屬性都賦值為紅色。為什么不是黑色呢?很簡單,如果是賦值為黑色,則每次插入就一定會破壞紅黑樹的性質。可是如果賦值為紅色就不一定啦!可是,紅黑樹的性質依然可能被破壞,所以我們可以再每次的插入操作之后執行insertRB_fixup操作,來檢查紅黑樹的性質是否被破壞,如果被破壞了,就通過對部分節點的調整來恢復紅黑樹的性質。
調整的可能情況有:
如過插入的節點是根節點。此時因為我們每次插入的節點都是紅色,會破壞性質2)。這是可以簡單的將根節點的顏色改為黑色來恢復紅黑樹的性質。
如果插入的節點不是根節點,且這個節點的父節點是黑節點。這時沒有破壞任何性質。不需要調整。
如果插入的節點不是根節點,且這個節點的父節點是紅節點。這時性質4)被破壞。這時可以再細分為三種情況(用z表示插入的節點,且設z為左孩子):
①z的叔叔y是紅色的。
②z的叔叔y是黑色的,而且z是右孩子
③z的叔叔y是黑色的,而且z是左孩子
對於①因為z的父節點和叔叔節點都是紅色的,則z的祖父節點是存在的且是黑色的。可以講z的父節點和叔叔節點都有紅色改為黑色,再將z的祖父節點有黑色改為紅色(這樣就不會破壞性質5)),現在z的父親節點是黑色的了,所以對z來說沒有破壞紅黑是的性質。不過z的祖父被改為了紅色,則又有可能破壞性質4)。同樣的問題向上轉移了。如果還是情況①,則繼續向上轉移。直到不再是情況①,最終的結果又四種可能:㈠變為情況②。㈡變為情況③(z指示的節點需要變化一下)。㈢在節點上移到根節點的子節點,因為根是黑的,所以問題結束。㈣z上移到根節點,這是需要將根節點重新改為黑色。
對於②,只需對z的父節點左旋一下,即可轉換為情況③。
對於③,可以將z的顏色父節點改為黑色,再將z的祖父節點改為紅色,之后再對z的祖父節點右旋轉一下即可恢復性質4)。
刪除:(首先是類似二叉樹的刪除,然后調整。二叉樹的任意節點刪除會轉換為只對有nil子節點的節點的刪除,具體參考二叉查找樹的刪除)
1.如果刪除的節點是紅色的,則節點刪除后,紅黑樹的性質沒有被破壞。
2.如果刪除的節點y是黑色的,2)、4)性質可能被破壞。在y被刪除之前,y的孩子只有兩種可能的情況:①有唯一的一個非nil的子節點。②沒有非nil的子節點。
用x表示刪除y節點后,代替y位置的節點。可分兩種情況:
Ⅰ.x為紅色,則是在①的情況下,即y有一個唯一的孩子x,且x為紅節點。此時只需將x的顏色由紅色改為黑色,即可恢復紅黑樹的性質。
Ⅱ.x為黑色,又如果x是根節點,則紅黑樹的性質沒有被破壞。如果x不是根節點,則x的父節點一定存在。又因為x是黑節點,所以此時x的父節點一定還存在另一個非nil的節點,
否則違反性質5)。
對x是黑節點,且x的父節點都存在的情況,可以再分成四類,不是一般性的,可以先假設x是父節點的left。
㈠:x的兄弟w是紅色的(因為w是紅色的,所以x和w的父節點是黑色的)
㈡:x的兄弟w是黑色的,而且w的兩個孩子都是黑的
㈢:x的兄弟w是黑色的,而且w的左孩子是紅色的,右孩子是黑色的。
㈣:x的兄弟w是黑色的,而且w色右孩子是紅色的
現在對刪除紅黑樹節點可能損壞二叉樹的性質5)的解決方法進行分析。如果刪除了一個黑節點且破壞了性質5),即造成了包含x節點的路徑比其他路徑少了一個黑的節點。解決方法
可以歸納為兩種:①將包含x的節點的路徑的黑節點都增加一。②將其他不包含x的路徑的黑節點數都減少一。通過這兩種解救方法就可以使黑節點數在此平衡。
解決的算法見算法導論的173業,當然也可以看下面我的代碼。這個算法的主體部分是對上面的㈠、㈡、㈢、㈣情況的解決,其他的情況都很容易解決。
對於㈠,通過一次旋轉,並對兩個節點的顏色交換,可以轉換為情況㈡或㈢或㈣。
對於㈢,通過一次旋轉,並對兩個節點的顏色交換,可以轉換為請款㈣。
對於㈣,通過一次旋轉,並對兩個節點的顏色交換,同時再把x的兄弟節點的右子節點的顏色由紅色變為黑色,就可以恢復紅黑樹的性質5)。由顏色的變化就可以發現這兒額外的將一個紅節點改為了黑節點。所以屬於通過方法①,來恢復紅黑樹的性質的。其實如果忽略每個節點的數據,只關心每個節點的顏色,這個方法可以簡單的理解為從x的父節點的另一顆子樹哪兒找到一顆紅的節點,將其變為黑色,並插入x為根的那顆子樹。但是,為了不破壞紅黑樹的數據性質,所以通過顏色的交換和節點的旋轉來實現。
對於㈡,先將x的兄弟節點的顏色改為紅色,這樣包含x的兄弟節點的所有路徑的黑節點數也都減少了一個,所以現在問題變為包含x的父親的節點的所有路徑的黑節點數都少了一個。這樣可以看成x就向上移了一層。然后再判斷新的x屬於那種情況,如果是㈠、㈢、㈣,則問題解決,如果還是㈡,則繼續上移。所以㈡的結束有兩種可能,一是在某次上移之后變為了情況㈠、㈢、㈣后解決,二是,一直上移到根節點后結束。這時,所有不包括x節點的路徑的黑節點的數目都減少了1,紅黑樹的性質5)得以恢復。這種解決方法屬於解決方法②。
寫了這么多,全是純粹的文字。如果不好理解配合和算法導論一起看。(估計以后我自己看的時候,也需要這樣。。。囧)
不多說了,貼代碼:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define MAXSIZE 1000
typedef int ElemType;
#define RED 0
#define BLACK 1
typedef struct RBTNode
{
char color;
ElemType data;
struct RBTNode * p;
struct RBTNode * left;
struct RBTNode * right;
}RBTNode, * PRBTNode;
typedef struct RBTree
{
PRBTNode root;
PRBTNode nil; //統一的空節點,該節點是黑的
}RBTree, * PRBTree;
int leftRotate (PRBTree tree, PRBTNode t);
int rightRotate (PRBTree tree, PRBTNode t);
PRBTNode insertRB (PRBTree tree, ElemType d);
int insertRB_fixup (PRBTree tree, PRBTNode t);
int createRBTree (PRBTree tree, ElemType d[], int n);
int initRB (PRBTree tree);
PRBTNode maximum (PRBTree tree, PRBTNode t);
PRBTNode minimum (PRBTree tree, PRBTNode t);
PRBTNode next (PRBTree tree, PRBTNode t);
PRBTNode precursor (PRBTree tree, PRBTNode t);
int walkNext (PRBTree tree);
int inOrderWalk (PRBTree tree, PRBTNode t);
int deleteRB_fixup (PRBTree tree, PRBTNode c);
PRBTNode deleteRB (PRBTree tree, PRBTNode t);
int main ()
{
PRBTNode p;
int d[MAXSIZE];
int n = 0;
int i;
RBTree tree;
initRB(&tree);
/*
insertRB(&tree, 11);
insertRB(&tree, 2);
insertRB(&tree, 14);
insertRB(&tree, 1);
insertRB(&tree, 7);
insertRB(&tree, 15);
insertRB(&tree, 5);
insertRB(&tree, 8);
insertRB(&tree, 4);
*/
p= insertRB(&tree, 26);
insertRB(&tree, 17);
insertRB(&tree, 41);
insertRB(&tree, 14);
insertRB(&tree, 21);
insertRB(&tree, 30);
insertRB(&tree, 47);
insertRB(&tree, 10);
insertRB(&tree, 16);
insertRB(&tree, 19);
insertRB(&tree, 23);
insertRB(&tree, 28);
insertRB(&tree, 38);
insertRB(&tree, 7);
insertRB(&tree, 12);
insertRB(&tree, 15);
insertRB(&tree, 20);
insertRB(&tree, 3);
insertRB(&tree, 35);
insertRB(&tree, 39);
srand(time(NULL));
/*
puts("請輸入數據的個數:");
scanf("%d",&n);
printf("隨機生成的%d個數據是:\n",n);
for (i = 0; i < n; i++)
{
d[i] = rand()%1000;
printf("%d ",d[i]);
}
puts("");
puts("建樹開始");
createRBTree(&tree, d, n);
*/
inOrderWalk(&tree,tree.root);
puts("");
printf("根是%d \n",tree.root->data);
printf("刪除%d后:",p->data);
deleteRB(&tree, p);
inOrderWalk(&tree,tree.root);
puts("");
printf("根是%d \n",tree.root->data);
return 0;
}
PRBTNode insertRB (PRBTree tree, ElemType d)
{//插入元素
//!!!記得插入的元素的初始化,p指向為父母節點,left和right賦值為NULL。
PRBTNode t = NULL;
PRBTNode p = NULL;
int flag = 0; //用來表示插入在左邊的樹還是右邊的樹
t = tree->root;
//插入的節點是root,並做相應的初始化
if (tree->root == tree->nil)
{
tree->root = (PRBTNode)malloc(sizeof(RBTNode));
tree->root->data = d;
tree->root->color = BLACK;
tree->root->p = tree->root->left =tree->root->right = tree->nil;
return tree->root;
}
while (t != tree->nil)
{
p = t;
if (d < t->data)
{
flag = 0;
t = t->left;
}
else
{
if (d > t->data)
{
flag = 1;
t = t->right;
}
else
{
if ( (flag=rand()%2) == 0)
t = t->left;
else
t = t->right;
}
}
}//while
//將t指向帶插入節點的地址,並初始化
t = (PRBTNode)malloc(sizeof(RBTNode));
t->data = d;
t->color = RED;
t->p = p;
t->left = t->right = tree->nil;
if (!flag)
p->left = t;
else
p->right = t;
insertRB_fixup(tree, t);
return t;
}
int insertRB_fixup (PRBTree tree, PRBTNode t)
{//插入的節點可能破壞紅黑樹的性質。該函數檢測插入的節點是否破壞了紅黑樹的性質。如果破壞了,就對樹進行調整,使其滿足紅黑樹的性質
while (t->p->color == RED) //只有插入節點的父親是紅色的才會破壞紅黑樹的性質(4.如果一個結點是紅的,那么它的倆個兒子都是黑的)
{
if (t->p->p->left == t->p) //插入節點的父節點本身是left
{
if (t->p->p->right->color == RED) //case 1
{
t = t->p->p;
t->left->color = t->right->color = BLACK;
t->color = RED;
}
else
{
if (t->p->right == t) //case 2
{//將case 2轉換為了case 3
t = t->p; //這步賦值是為了在轉換為case 3時,t指向的是下面的紅節點,和case 3的情況相一致
leftRotate(tree, t);
}
//case 3
t->p->color = BLACK;
t->p->p->color = RED;
rightRotate(tree, t->p->p);
}
}//if
else //插入節點的父節點本身是right
{
if (t->p->p->left->color == RED) //case 1
{
t = t->p->p;
t->left->color = t->right->color = BLACK;
t->color = RED;
}
else
{
if (t->p->left == t) //case 2
{//將case 2轉換為了case 3
t = t->p; //這步賦值是為了在轉換為case 3時,t指向的是下面的紅節點,和case 3的情況相一致
rightRotate(tree, t);
}
//case 3
t->p->color = BLACK;
t->p->p->color = RED;
leftRotate(tree, t->p->p);
}
}//else
}//while
tree->root->color = BLACK;
return 0;
}
int leftRotate (PRBTree tree, PRBTNode t)
{
PRBTNode c; //左旋,c指向t的right
c = t->right;
if (t->right == tree->nil) //左旋,t的right不能為空
return 1;
//這個if-else用於將t的父親節點的left或right點指向c,如果t的父節點為不存在,則樹的root指向c
if (t->p != tree->nil) //判斷t是否為root
{
if (t->p->left == t) //判斷t是t的父節點的left還是right
t->p->left = c;
else
t->p->right = c;
}
else
tree->root = c;
c->p = t->p; //更新c的父節點
t->right = c->left;
if (c->left != tree->nil)
c->left->p = t;
c->left = t;
t->p = c;
return 0;
}
int rightRotate (PRBTree tree, PRBTNode t)
{
PRBTNode c; //右旋,c指向t的left
c = t->left;
if (t->left == tree->nil) //右旋,t的left不能為空
return 1;
//這個if-else用於將t的父親節點的left或right點指向c,如果t的父節點為不存在,則樹的root指向c
if (t->p != tree->nil) //判斷t是否為root
{
if (t->p->left == t) //判斷t是t的父節點的left還是right
t->p->left = c;
else
t->p->right = c;
}
else
tree->root = c;
c->p = t->p; //更新c的父節點
t->left = c->right;
if (c->right != tree->nil)
c->right->p = t;
c->right = t;
t->p = c;
return 0;
}
int createRBTree (PRBTree tree, ElemType d[], int n)
{//用元素的插入建樹
int index = -1;
int tmp = -1;
srand(time(NULL));
while (n--)
{
index =(int) rand()%(n+1);//此時共有n+1個數據
tmp = d[index];
d[index] = d[n];
d[n] = tmp;
insertRB(tree, d[n]);
printf("插入%d\t",d[n]);
}
puts("");
return 0;
}//createRBTree
int initRB (PRBTree tree)
{//紅黑樹的初始化
if (tree == NULL)
return 0;
tree->nil = (PRBTNode)malloc(sizeof(RBTNode));
tree->nil->color = BLACK;
tree->root = tree->nil;
return 0;
}//initRB
PRBTNode minimum (PRBTree tree, PRBTNode t)
{//返回最小值,如果t是NULL返回NULL
if (t == tree->nil)
return NULL;
while (t->left != tree->nil)
t = t->left;
return t;
}//minimum
PRBTNode maximum (PRBTree tree, PRBTNode t)
{//返回最大值,如果t是NULL返回NULL
if (t == tree->nil)
return NULL;
while (t->right != tree->nil)
t = t->right;
return t;
}//maximum
PRBTNode next (PRBTree tree, PRBTNode t)
{//給出t的后繼的節點。如果沒有后繼,就返回NULL
PRBTNode p; //指示父節點
if (t->right == tree->nil)
{
p = t->p;
while (p != tree->nil && p->right == t)
{
t = p;
p = t->p;
}
return p; //如果是最后一個元素,p的值為NULL
}
else
return minimum(tree, t->right);
}//next
PRBTNode precursor (PRBTree tree, PRBTNode t)
{//返回節點t前驅,如果沒有前驅,就返回NULL
PRBTNode p;
if (t->left == tree->nil)
{
p = t->p;
while (p != tree->nil && p->left == t)
{
t = p;
p = t->p;
}
return p;
}
else
return maximum(tree, t->left);
}//precusor
int walkNext (PRBTree tree)
{//遍歷二叉搜索樹。先找到最小的元素,再通過用next()求后繼來遍歷樹
PRBTNode t;
t = minimum(tree,tree->root);
while (t != tree->nil)
{
printf("%d ",t->data);
if (t->color == BLACK)
printf("B\t");
else
printf("R\t");
t = next(tree,t);
}
return 0;
}//walkNext
PRBTNode deleteRB (PRBTree tree, PRBTNode t)
{//刪除數據。要求給處數據節點的指針
PRBTNode c = NULL; //c指向要取代被刪除節點的子節點
PRBTNode d = NULL;
ElemType tmp;
if (t == tree->nil)
return NULL;
//d指向真正要刪除的元素的下標。如果t的left和right都有值,則轉化為刪除t的后繼節點,並把后繼節點的內容復制給t指向的節點。
//而其他情況則直接刪除t指向的節點
if (t->left != tree->nil && t->right != tree->nil)
{
d = next(tree, t);
//因為實際操作要刪除的是d指向的節點,所以先交換data
tmp = d->data;
d->data = t->data;
t->data = tmp;
}
else
d = t;
//確定c的指向
if (d->left == tree->nil)
c = d->right;
else
c = d->left;
//將c的父親指針設為d的父親指針,c不會為空(因為存在nil節點)
c->p = d->p;
if (d->p != tree->nil)
{
if (d->p->left == d)
d->p->left = c;
else
d->p->right = c;
}
else
tree->root = c;
if (d->color == BLACK)
deleteRB_fixup(tree, c);
return d;
}//deleteRB
int deleteRB_fixup (PRBTree tree, PRBTNode c)
{
PRBTNode b; //兄弟節點
while (c != tree->root && c->color == BLACK)
{
if (c == c->p->left)
{
b = c->p->right;
if (b->color == RED) //case 1
{//b節點是紅的,可以說明c和b的父親節點是黑的。通過以下的操作可以吧case 1轉換為case 2,3,4中的一個
b->color = BLACK;
c->p->color = RED;
leftRotate(tree, c->p);
b = c->p->right; //新的兄弟節點,這個節點一定是黑色的。這個節點之前是紅色節點的兒子
}
if (b->right->color == BLACK && b->left->color == BLACK) //case 2
{
b->color = RED; //將c的父節點的另一顆子樹黑節點減少1
c = c->p; //將c上移。上移之后,c的黑高度相同了(因為另一顆子樹的根節點有黑邊為紅)
}
else //case 3或case 4
{
if (b->right->color == BLACK) //case 3 通過以下操作將case 3 轉化為case 4
{
b->color = RED;
b->left->color = BLACK;
rightRotate(tree, b);
b = c->p->right;
}
//case 4
//通過下面的操作,紅黑樹的性質恢復
b->color = b->p->color;
b->p->color = BLACK;
b->right->color = BLACK;
leftRotate(tree, c->p);
c = tree->root; //紅黑樹性質恢復,結束循環。不用break,是因為while結束后還要執行c->color = BLACK;
}
}//if (c == c->p->left)
else
{
b = c->p->left;
if (b->color == RED) //case 1
{//b節點是紅的,可以說明c和b的父親節點是黑的。通過以下的操作可以吧case 1轉換為case 2,3,4中的一個
b->color = BLACK;
c->p->color = RED;
rightRotate(tree, c->p);
b = c->p->left; //新的兄弟節點,這個節點一定是黑色的。這個節點之前是紅色節點的兒子
}
if (b->right->color == BLACK && b->left->color == BLACK) //case 2
{
b->color = RED; //將c的父節點的另一顆子樹黑節點減少1
c = c->p; //將c上移。上移之后,c的黑高度相同了(因為另一顆子樹的根節點有黑邊為紅)
}
else //case 3或case 4
{
if (b->left->color == BLACK) //case 3 通過以下操作將case 3 轉化為case 4
{
b->color = RED;
b->right->color = BLACK;
leftRotate(tree, b);
b = c->p->left;
}
//case 4
//通過下面的操作,紅黑樹的性質恢復
b->color = b->p->color;
b->p->color = BLACK;
b->left->color = BLACK;
rightRotate(tree, c->p);
c = tree->root; //紅黑樹性質恢復,結束循環。不用break,是因為while結束后還要執行c->color = BLACK;
}
}//else
}
c->color = BLACK;
return 0;
}//deleteRB_fixup
int inOrderWalk (PRBTree tree, PRBTNode t)
{//中序遍歷
if (t == tree->nil)
return 0;
putchar('(');
inOrderWalk (tree, t->left);
putchar(')');
printf(" %d ",t->data);
if (t->color == BLACK)
printf("B");
else
printf("R");
putchar('(');
inOrderWalk (tree, t->right);
putchar(')');
return 0;
}