看到一篇很好的文章html
文章來源:http://www.360doc.com/content/15/0730/00/14359545_488262776.shtmlnode
紅黑樹是一種高效的索引樹,多於用關聯數組、STL容器的數據結構中。說到紅黑樹不得不提下二分查找樹和AVL樹。二分查找樹的時間複雜度爲O(h)(h爲樹的高度),若是樹的高度較低,這些集合操做會很快,可是若是樹的高度很高時,效率就會很低,特別是當節點n必定時,樹的高度有時會很大,固然最好的狀況是咱們但願樹的高度爲lg(n),因而咱們能夠對查找樹來進行旋轉操做來使其平衡,因而引出了AVL樹,AVL樹是平衡樹的一種,具備如下性質:它是一棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,而且左右兩個子樹都是一棵AVL樹。這樣的話咱們能夠把h換成lgn,因而AVL樹的查找時間複雜度就變爲O(lgn)了,但是有時太極端就很差了,AVL追求了高度的平衡,當插入一個元素的時候,可能須要旋轉不少次來追求調試平衡的效果。爲了解決這個問題,咱們引出了紅黑樹,紅黑樹也是平衡樹的一種,時間複雜度也是O(lgn)可是它只要求沒有一種路徑會比其它路徑長出2倍,因此它追求的是局部的平衡;實際上插入AVL樹和紅黑樹的速度取決於你所插入的數據.若是你的數據分佈較好,則比較宜於採用AVL樹(例如隨機產生系列數),可是若是你想處理比較雜亂的狀況,則紅黑樹是比較快的,由於紅黑樹對已經處理好的數據從新平衡減小了不心要的操做.算法
紅黑樹(RBT)的定義:它或者是一顆空樹,或者是具備一下性質的二叉查找樹:數組
1.節點非紅即黑。數據結構
2.根節點是黑色。函數
3.全部NULL結點稱爲葉子節點,且認爲顏色爲黑。性能
4.全部紅節點的子節點都爲黑色。spa
5.從任一節點到其葉子節點的全部路徑上都包含相同數目的黑節點。指針
看完紅黑樹的定義是否是可暈?怎麼這麼多要求!!這怎麼約束啊?我剛看到這5條約束,直接無語了,1-三、4還好說,第5點是怎麼回事啊?怎麼約束?整這麼複雜的條件好乾啥啊?我來簡單說說呵:第3條,顯然這裏的葉子節點不是日常咱們所說的葉子節點,如圖標有NIL的爲葉子節點,爲何不按常規出牌,由於按通常的葉子節點也行,但會使算法更復雜;第4條,即該樹上決不容許存在兩個連續的紅節點;第5條,好比圖中紅8到1左邊的葉子節點的路徑包含2個黑節點,到6下的葉子節點的路徑也包含2個黑節點。全部性質1-5合起來約束了該樹的平衡性能--即該樹上的最長路徑不可能會大於2倍最短路徑。爲何?由於第1條該樹上的節點非紅即黑,因爲第4條該樹上不容許存在兩個連續的紅節點,那麼對於從一個節點到其葉子節點的一條最長的路徑必定是紅黑交錯的,那麼最短路徑必定是純黑色的節點;而又第5條從任一節點到其葉子節點的全部路徑上都包含相同數目的黑節點,這麼來講最長路徑上的黑節點的數目和最短路徑上的黑節點的數目相等!而又第2條根結點爲黑、第3條葉子節點是黑,那麼可知:最長路徑<=2*最短路徑。一顆二叉樹的平衡性能越好,那麼它的效率越高!顯然紅黑樹的平衡性能比AVL的略差些,可是通過大量試驗證實,實際上紅黑樹的效率仍是很不錯了,仍能達到O(logN),這個我不知道,我如今不可能作過大量試驗,只是聽人家這樣說,O(∩_∩)O哈哈~但你至少知道他的時間複雜度必定小於2O(logN)!調試
上邊的性質看個10遍,看懂看透徹再看操做!
插入操做
因爲性質的約束:插入點不能爲黑節點,應插入紅節點。由於你插入黑節點將破壞性質5,因此每次插入的點都是紅結點,可是若他的父節點也爲紅,那豈不是破壞了性質4?對啊,因此要作一些「旋轉」和一些節點的變色!另爲敘述方便咱們給要插入的節點標爲N(紅色),父節點爲P,祖父節點爲G,叔節點爲U。下邊將一一列出全部插入時遇到的狀況:
情形1:該樹爲空樹,直接插入根結點的位置,違反性質1,把節點顏色有紅改成黑便可。
情形2:插入節點N的父節點P爲黑色,不違反任何性質,無需作任何修改。
情形1很簡單,情形2中P爲黑色,一切安然無事,但P爲紅就不同了,下邊是P爲紅的各類狀況,也是真正要學的地方!
情形3:N爲紅,P爲紅,(祖節點必定存在,且爲黑,下邊同理)U也爲紅,這裏不論P是G的左孩子,仍是右孩子;不論N是P的左孩子,仍是右孩子。
操做:如圖把P、U改成黑色,G改成紅色,未結束。
解析:N、P都爲紅,違反性質4;若把P改成黑,符合性質4,顯然左邊少了一個黑節點,違反性質5;所以咱們把G,U都改成相反色,這樣一來經過G的路徑的黑節點數目沒變,即符合四、5,可是G變紅了,若G的父節點又是紅的不就有違反了4,是這樣,因此通過上邊操做後未結束,需把G做爲起始點,即把G看作一個插入的紅節點繼續向上檢索----屬於哪一種狀況,按那種狀況操做~要麼中間就結束,要麼知道根結點(此時根結點變紅,一根結點向上檢索,那木有了,那就把他變爲黑色吧)。
情形4:N爲紅,P爲紅,U爲黑,P爲G的左孩子,N爲P的左孩子(或者P爲G的右孩子,N爲P的左孩子;反正就是同向的)。
操做:如圖P、G變色,P、G變換即左左單旋(或者右右單旋),結束。
解析:要知道通過P、G變換(旋轉),變換後P的位置就是當年G的位置,因此紅P變爲黑,而黑G變爲紅都是爲了避免違反性質5,而維持到達葉節點所包含的黑節點的數目不變!還能夠理解爲:也就是至關於(只是至關於,並非實事,只是爲了更好理解;)把紅N頭上的紅節點移到對面黑U的頭上;這樣即符合了性質4也不違反性質5,這樣就結束了。
情形5:N爲紅,P爲紅,U爲黑,P爲G的左孩子,N爲P的右孩子(或者P爲G的右孩子,N爲P的左孩子;反正兩方向相反)。
操做:須要進行兩次變換(旋轉),圖中只顯示了一次變換-----首先P、N變換,顏色不變;而後就變成了情形4的狀況,按照狀況4操做,即結束。
解析:因爲P、N都爲紅,經變換,不違反性質5;而後就變成4的情形,此時G與G如今的左孩子變色,並變換,結束。
刪除操做
咱們知道刪除需先找到「替代點」來替代刪除點而被刪除,也就是刪除的是替代點,而替代點N的至少有一個子節點爲NULL,那麼,若N爲紅色,則兩個子節點必定都爲NULL(必須地),那麼直接把N刪了,不違反任何性質,ok,結束了;若N爲黑色,另外一個節點M不爲NULL,則另外一個節點M必定是紅色的,且M的子節點都爲NULL(按性質來的,不明白,本身分析一下)那麼把N刪掉,M佔到N的位置,並改成黑色,不違反任何性質,ok,結束了;若N爲黑色,另外一個節點也爲NULL,則把N刪掉,該位置置爲NULL,顯然這個黑節點被刪除了,破壞了性質5,那麼要以N節點爲起始點檢索看看屬於那種狀況,並做相應的操做,另還需說明N爲黑點(也許是NULL,也許不是,都同樣),P爲父節點,S爲兄弟節點(這個我真想給兄弟節點叫B(brother)多好啊,不過人家圖就是S我也不能改,在重畫圖,太浪費時間了!S也行呵呵,就當是sister也行,哈哈)分爲如下5中狀況:
情形1:S爲紅色(那麼父節點P必定是黑,子節點必定是黑),N是P的左孩子(或者N是P的右孩子)。
操做:P、S變色,並交換----至關於AVL中的右右中旋轉即以P爲中心S向左旋(或者是AVL中的左左中的旋轉),未結束。
解析:咱們知道P的左邊少了一個黑節點,這樣操做至關於在N頭上又加了一個紅節點----不違反任何性質,可是到經過N的路徑仍少了一個黑節點,須要再把對N進行一次檢索,並做相應的操做才能夠平衡(暫且無論往下看)。
情形2:P、S及S的孩子們都爲黑。
操做:S改成紅色,未結束。
解析:S變爲紅色後通過S節點的路徑的黑節點數目也減小了1,那個從P出發到其葉子節點到全部路徑所包含的黑節點數目(記爲num)相等了。可是這個num比以前少了1,由於左右子樹中的黑節點數目都減小了!通常地,P是他父節點G的一個孩子,那麼由G到其葉子節點的黑節點數目就不相等了,因此說沒有結束,需把P當作新的起始點開始向上檢索。
情形3:P爲紅(S必定爲黑),S的孩子們都爲黑。
操做:P該爲黑,S改成紅,結束。
解析:這種狀況最簡單了,既然N這邊少了一個黑節點,那麼S這邊就拿出了一個黑節點來共享一下,這樣一來,S這邊沒少一個黑節點,而N這邊便多了一個黑節點,這樣就恢復了平衡,多麼美好的事情哈!
情形4:P任意色,S爲黑,N是P的左孩子,S的右孩子SR爲紅,S的左孩子任意(或者是N是P的右孩子,S的左孩子爲紅,S的右孩子任意)。
操做:SR(SL)改成黑,P改成黑,S改成P的顏色,P、S變換--這裏相對應於AVL中的右右中的旋轉(或者是AVL中的左左旋轉),結束。
解析:P、S旋轉有變色,等於給N這邊加了一個黑節點,P位置(是位置而不是P)的顏色不變,S這邊少了一個黑節點;SR有紅變黑,S這邊又增長了一個黑節點;這樣一來又恢復了平衡,結束。
情形5:P任意色,S爲黑,N是P的左孩子,S的左孩子SL爲紅,S的右孩子SR爲黑(或者N是P的有孩子,S的右孩子爲紅,S的左孩子爲黑)。
操做:SL(或SR)改成黑,S改成紅,SL(SR)、S變換;此時就回到了情形4,SL(SR)變成了黑S,S變成了紅SR(SL),作情形4的操做便可,這兩次變換,其實就是對應AVL的右左的兩次旋轉(或者是AVL的左右的兩次旋轉)。
解析:這種狀況若是你按情形4的操做的話,因爲SR原本就是黑色,你沒法彌補因爲P、S的變換(旋轉)給S這邊形成的損失!因此我沒先對S、SL進行變換以後就變爲情形4的狀況了,何樂而不爲呢?
下面是c代碼:
#include stdio.h
#include stdlib.h
#define RED 0
#define BACK 1
typedef int Elemtype;
//定義一個紅黑樹的結點
typedef struct Red_Back_Tree
{
Elemtype e;
int color;
struct Red_Back_Tree * child[2];
}* RBT;
// 兩個節點變換函數
void conversion(RBT *T,int direction);
// 刪除一個節點的所用函數
int DeleteRBT(RBT *T,Elemtype e); // 刪除主(接口)函數
int find_replace_point(RBT gogal,RBT *l); // 尋找替代點
int keep_balance_for_delete(RBT *T,int direction); // 刪除的平衡操做
int do_with_start_point(RBT gogal,RBT *T,int direction); // 處理第一個起始點
// 插入一個節點的所用函數
int InsertRBT(RBT *T,Elemtype e); // 插入接口函數
int _InsertRBT(RBT *T,Elemtype e); // 插入主函數
int keep_balance_for_insert(RBT *T,int firdirection,Elemtype e);// 插入的平衡操做
RBT create_one_node(Elemtype e); // 新建一個節點
void conversion(RBT *T,int direction)
{
RBT f=(*T),s=f->child[direction],ss=s->child[!direction];
f->child[direction]=ss;
s->child[!direction]=f;
*T=s;
}
//★★★★★★★★★★★★★★★★★刪除操做★★★★★★★★★★★★★★★★★★★★★★★★★★★
int do_with_start_point(RBT gogal,RBT *T,int direction)
{
gogal->e=(*T)->e;
if(BACK==((*T)->color))
{
if(NULL!=(*T)->child[direction])
{
(*T)->e=(*T)->child[direction]->e;
free((*T)->child[direction]);
(*T)->child[direction]=NULL;
return 1;
}
else
{
free((*T));
*T=NULL;
return 0;
}
}
else
{
free((*T));
(*T)=NULL;
return 1;
}
}
int keep_balance_for_delete(RBT *T,int direction)
{
RBT p=(*T),b=p->child[!direction];
if(RED==b->color)
{
p->color=RED;
b->color=BACK;
// conversion(&p,!direction);//很恐怖的一個寫法,偶然中發現:這裏傳的地址是假的!不是T!!
// 考我怎麼這麼傻逼!!若是不是及時發現,到調試時將是無限恐怖
// 將是一個巨大的隱藏的BUG!!!將會帶來巨大的麻煩!!!
conversion(T,!direction);
return keep_balance_for_delete(&((*T)->child[direction]),direction);
}
else if(BACK==p->color && BACK==b->color &&
(NULL==b->child[0] || BACK==b->child[0]->color) &&
(NULL==b->child[1] || BACK==b->child[1]->color)) //這裏感受不美,就一次爲NULL卻每次要
{ //判斷是否爲NULL,不美……
b->color=RED;
return 0;
}
else if(RED==p->color &&
(NULL==b->child[0] || BACK==b->child[0]->color) &&
(NULL==b->child[1] || BACK==b->child[1]->color))
{
p->color=BACK;
b->color=RED;
return 1;
}
// 第一次調試
// 調試緣由:因爲刪除0點未按預料的操做應該是狀況④,卻按⑤操做
// 錯誤的地方:RED==b->child[!direction] ! 丟了->color 這個錯誤我上邊錯了幾回,不過編譯器報錯改了過來
// 此次的編譯器不報錯,看代碼也看不錯來,最後追究到這裏,一一對照才發現!!!
// else if(BACK==b->color && (NULL!=b->child[!direction] && RED==b->child[!direction]))
else if(BACK==b->color && (NULL!=b->child[!direction] && RED==b->child[!direction]->color))
{
b->color=p->color;
p->color=BACK;
b->child[!direction]->color=BACK;
conversion(T,!direction);
return 1;
}
else
{
b->child[direction]->color=p->color;
p->color=BACK;
conversion(&(p->child[!direction]),direction);//這裏的p寫的纔算不錯!即p也(*T)都行,同樣!
conversion(T,!direction);
return 1;
}
}
int find_replace_point(RBT gogal,RBT *l)
{
if(NULL!=(*l)->child[0])
{
if(find_replace_point(gogal,&(*l)->child[0])) return 1;
return keep_balance_for_delete(l,0);
//...
}
// 第二次調試---其實沒F5,F10,F11,根據結果猜想,到這裏看看還真是的!
// 調試緣由:刪除0好了,刪除1又錯了---2不見了,1還在
// 錯誤的地方:就在這裏,找到替代點,卻沒有「替代」,這等於把替代點刪了...
// 這裏很明顯,gogal這個刪除點指針根本就沒用...我當時忘了吧!!修改以下!
// else //替代點爲起始點
// {
// return do_with_start_point(l,1);
// }
else
{
return do_with_start_point(gogal,l,1);
}
}
int DeleteRBT(RBT *T,Elemtype e)
{
if(!(*T)) return -1;
else if(e>(*T)->e)
{
if(DeleteRBT(&((*T)->child[1]),e)) return 1;
return keep_balance_for_delete(T,1);
//...
}
else if(e<(*T)->e)
{
if(DeleteRBT(&((*T)->child[0]),e)) return 1;
return keep_balance_for_delete(T,0);
//...
}
else
{
if(NULL!=(*T)->child[1]) //真正的刪除點不是起始點,需找替代點
{
if(find_replace_point((*T),&((*T)->child[1]))) return 1;
return keep_balance_for_delete(T,1);
//...
}
else //真正的刪除點就是起始點
{
return do_with_start_point((*T),T,0);
}
}
}
//★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
//★★★★★★★★★★★★★★★★★★★插入操做★★★★★★★★★★★★★★★★★★★★★★★★★
RBT create_one_node(Elemtype e)
{
RBT p=(RBT)malloc(sizeof(struct Red_Back_Tree));
p->e=e; p->color=RED;
p->child[0]=p->child[1]=NULL;
return p;
}
int keep_balance_for_insert(RBT *T,int firdirection,Elemtype e)
{
RBT p=(*T)->child[firdirection],u=(*T)->child[!firdirection];
int secdirection=( (e>p->e) ? 1 : 0 ); // 查處第二個方向
if(NULL!=u && RED==u->color)
{
p->color=BACK;
u->color=BACK;
(*T)->color=RED;
return 1; //繼續...
}
else
{
if(firdirection!=secdirection) conversion(&((*T)->child[firdirection]),secdirection);
(*T)->color=RED; (*T)->child[firdirection]->color=BACK;
conversion(T,firdirection);
return 0;
}
}
int _InsertRBT(RBT *T,Elemtype e)
{
int info=0;
if(NULL==(*T)) //這裏只是包含這種狀況
{
*T=create_one_node(e);
(*T)->color=RED;
info=1;
}
else if(e>((*T)->e))
{
info=_InsertRBT(&(*T)->child[1],e);
if(info<1) return info;
else if(info==1)
{
if(BACK==((*T)->color)) info--;
else info++;
}
else
{
info=keep_balance_for_insert(T,1,e);
}
}
else if(e<((*T)->e))
{
info=_InsertRBT(&((*T)->child[0]),e);
if(info<1) return info;
else if(info==1)
{
if(BACK==((*T)->color)) info--;
else info++;
}
else
{
info=keep_balance_for_insert(T,0,e);
}
}
else return info=-1;
return info;
}
int InsertRBT(RBT *T,Elemtype e) //插入節點函數返回值: -1->改點已存在 0->成功插入
{
int info=0; // info: -1->已存在 0->結束 1->回溯到父節點 2->回溯到祖節點
//2011年11月30日9:13:47 昨天晚上最後又想來這裏這個if能夠不要便可,也就是把它也放到_InsertRBT
//內處理,在InsertRBT中有個判斷便可!即改爲下邊的寫法!
// if(NULL==(*T))
// {
// *T=create_one_node(e);
// (*T)->color=BACK;
// }
// else
// {
// info=_InsertRBT(T,e); // 通過再三思考,這裏info的返回值只可能爲:-1 0 1
// if(info>0) (*T)->color=BACK,info=0; // 查看根節點是否爲紅
// }
info=_InsertRBT(T,e);
if(info==1) (*T)->color=BACK,info=0;
// 爲了防止根結點變爲紅,它實際上是處理了兩種狀況的後遺症
// 分別是:③狀況回溯上來,根節點變紅 ①狀況插入點即爲根節點,爲紅
// 這裏沒有直接把根結點變黑,主要是爲了與_InsertRBT保持一致的寫法,其實都行!
return info;
}
//★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
//
RBT queue[1000];
void print(RBT cur)
{
int front=0,rear=0;
int count=1,temp=0;
if(NULL==cur)
{
printf("NULL\n");
return ;
}
queue[rear]=cur;
while(front<=rear)
{
cur=queue[front++]; count--;
if(NULL!=cur->child[0]) queue[++rear]=cur->child[0],temp++;
if(NULL!=cur->child[1]) queue[++rear]=cur->child[1],temp++;
printf("%d color->",cur->e);
if(BACK==cur->color) printf("BACK |");
else printf("RED |");
if(0==count)
{
count=temp;
temp=0;
printf("\n");
}
}
}
//
//
int main()
{
RBT T=NULL;
int i,nodenum=100;
print(T);
printf("\n");
printf("\n插入操做\n");
for(i=0;i
{
InsertRBT(&T,i);
printf("插入%d\n",i);
print(T);
printf("\n");
}
// print(T);
printf("\n刪除操做:\n");
for(i=0;i
{
DeleteRBT(&T,i);
printf("刪除%d\n",i);
print(T);
printf("\n");
}
return 0;}