前言
[爲什麼寫這篇]
之前在知乎上看過一個提問:爲什麼紅黑樹比AVL樹用的場景更爲廣泛?其實,這兩者應用場景都挺廣泛的。紅黑樹在 STL
和 Linux
都有一定的運用。而AVL樹也在 Windows進程地址空間管理
中得到了使用。既然紅黑樹和AVL樹這麼厲害,就要進一步瞭解一下它們到底是什麼。
基礎準備
[需要懂點數據結構哦]
紅黑樹和AVL都是來源於二叉排序樹,關於二叉搜索樹的相關知識本文將會對一些簡單的概念和操作進行分析,更多的細節需要大家自己去進一步瞭解。(ps:算法導論或許是一個不錯的選擇)
二叉排序樹
[一切爲了查找、插入、刪除方便]
我們都知道,線性表分爲無序線性表和有序線性表。
無序線性表的數據並不是按升序或者降序來排列的,所以在插入和刪除時,沒有什麼必須遵守的規矩而可以插入在數據尾部或者刪除在數據尾部(將待刪除的數據和最後一個數據交換位置),但是在查找的時候,需要遍歷整個數據集,影響了效率。
有序線性表的數據則想法,查找的時候因爲數據有序,可以用二分法、插值法、斐波那契查找法來實現,但是,插入和刪除需要維護有序的結構,會耗費大量的時間。
爲了提高插入和刪除的效率,二叉排序樹登場了。
二叉排序樹的定義
二叉排序樹 (BST)
是一棵具有下列性質的二叉樹。
- 若它的左子樹不空,則左子樹上所有結點的值均小於它的根結構的值
- 若它的右子樹不空,則右子樹上所有結點的值均大於它的根結構的值
- 它的左子樹和右子樹都是二叉排序樹
定義中最爲關鍵的特點是,左子樹結點一定比父結點小,右子樹結點一定比父結點大
二叉排序樹查找
通過觀察上面的二叉排序樹,可以知道,查找樹中一個值,可以從根結點開始查找,和根結點的值做比較,比根結點的值小,就在根結點的左子樹中查找,比根結點的值大,就在根結點的右子樹中查找。其他結點的行爲與根結點的行爲也是一樣的。以此出發,可以得到遞歸算法:
- 如果樹是空的,則查找結束,無匹配。
- 如果被查找的值和根結點的值相等,查找成功。否則就在子樹中繼續查找。如果被查找的值小於根結點的值就選擇左子樹,大於根結點的值就選擇右子樹。
在理想情況下,每次比較過後,樹會被砍掉一半,近乎折半查找。
遍歷打印可以使用 中序遍歷
,打印出來的結果是從小到大的有序數組。
二叉排序樹插入
二叉排序的插入是建立在二叉排序的查找之上的,原因很簡單,添加一個結點到合適的位置,就是通過查找發現合適位置,把結點直接放進去。
先來說一下插入函數,SearchBST(BiTree T, int key,BiTree f,BiTree *p)
中指針p具有非常重要的作用:
- 若查找的key已經有在樹中,則p指向該數據結點。
- 若查找的key沒有在樹中,則p指向查找路徑上最後一個結點,而這裏的最後一個結點的位置和key應該被放入的位置存在着簡單關係(要麼當樹空時直接插入作爲根結點,
要麼當樹非空時新結點作爲查找路徑終止結點的左孩子或者右孩子插入
)。
將上面的這些描述轉化爲代碼:
InsertBST(BiTree *T,int key)
{
BiTree p,s;
if(!SearchBST(*T,key,NULL,&p)) /* 查找不成功 */
{
s=(BiTree)malloc(sizeof(BiTree));
s->data=key;
s->lchild=s->rchild=NULL;
if(!p) /* 樹爲空 */
*T=s; /* 在空樹中插入一個新結點作爲根結點 */
else if(key<p->data)
p->lchild=s;
else
p->rchild=s;
return TRUE;
}
else
return FALSE; /*樹中已經有相應的key,不用插入*/
}
藉助了二叉排序樹的查找,輕鬆的找到新結點該放在哪個位置,然後把新結點對號入座放進去,就完成了二叉排序樹的插入操作。這中間並不會引起二叉樹其他部分的結構變化。
二叉排序樹刪除
二叉樹的刪除可不再像二叉樹的插入那麼容易了,以爲刪除某個結點以後,會影響到樹的其它部分的結構,比如刪掉45,然後45的子孫們37、39、53將何處安放?
刪除的時候需要考慮一下幾種情況:刪除的結點只有左子樹、刪除的結點只有右子樹、刪除的結點既有左子樹又有右子樹。
考慮前兩種情況,直接將左子樹或者右子樹替換被刪除的結點即可。
第三種情況,有左子樹和右子樹的情況。
當把二叉排序樹進行中序遍歷,在序列中可以得到一個刪除結點s的直接前驅(或者直接後繼),用直接前驅p來替代s。
重點來看一下二叉排序樹的結點
/* 處理刪除結點後子樹拼接的三種情況 */
Status Delete(BiTree *p)
{
BiTree q,s;
if((*p)->rchild==NULL) /* 只有左子樹則只重接左子樹 */
{
q=*p;*p=(*p)->lchild;free(q);
}else if((*p)->lchild==NULL) /* 只有右子樹則只重接右子樹 */
{
q=*p;*p=(*p)->lchild==NULL);free(q);
}else /* 左右子樹均不爲空 */
{
q=*p;s=(*p)->lchild;
while(s->rchild) /* 找到左子樹的右盡頭(找到直接前驅)*/
{
q=s;s=s->rchild;
}
(*p)->data=s->data; /* s指向被刪除結點的直接前驅 */
if(q!=*p)
q->rchild=s->lchild; /* 重接q的右子樹 */
else
q->lchild=s->lchild; /* 重接q的左子樹 */
free(s);
}
return TRUE;
}
這段大碼的內容分析了左右子樹均不爲空的情況,目的就是在與找到p的 左子樹的右盡頭
(因爲右盡頭是待刪除結點的前驅結點),這個尋找的步驟就是while循環裏面指針s指向自身的右孩子:s=s->rchild.
找到右盡頭後,就要把右盡頭的左子樹(因爲是右盡頭了,所以右盡頭只有左子樹沒有右子樹)拼接到q上,完成樹的移植工作。
二叉排序樹極端情況
二叉排序樹的優點在於保持了插入刪除不用移動元素只要修改指針的優點。在查找上,查找次數等於待查找的結點在二叉排序樹的層級。
來看一種極端情況:
這種有序數組,查找最後一個結點99需要經歷非常多的層級,其實查找次數還是偏多的。這樣的情況下,樹是不平衡的,右側太重。
我們爲了提高二叉排序樹的查找效率,需要把樹構建得更爲平衡,從而不出現左右偏重的情況。
這就引出了AVL樹和紅黑樹這兩種平衡二叉樹了。
AVL樹
AVL樹的定義
平衡二叉樹 (Height-Balanced Binary Search Tree)
是一種二叉排序樹,其中每一個結點的左子樹和右子樹的高度差不超過1(小於等於1)。
二叉樹的平衡因子 (Balance Factor)
等於該結點的左子樹深度減去右子樹深度的值稱爲平衡因子。平衡因子只可能是-1,0,1。
距離插入結點最近的,且平衡因子的絕對值大於1的結點爲根的自述,稱爲最小不平衡子樹。
AVL樹的實現思路
平衡二叉樹就是二叉樹的構建過程中,每當插入一個結點,看是不是因爲樹的插入破壞了樹的平衡性,若是,則找出最小不平衡樹。在保持二叉樹特性的前提下,調整最小不平衡子樹中各個結點之間的鏈接關係,進行相應的旋轉,使之成爲新的平衡子樹。簡記爲: 步步調整,步步平衡
。
AVL樹的實現過程
[左旋與右旋]
如上面提到的非平衡二叉樹,查找的層級太多,如何減少這些曾經呢?這就要提到左旋和右旋了。先來看張圖
左旋和右旋的過程我們可以看到平衡因子從(0,1,2)變爲(0,0,0),即是一種將非平衡狀態轉換爲平衡狀態的過程,這也是AVL樹步步調整的核心。
再來觀察一種複雜的情況
新插入一個結點17,使得13的BF(-2)和21的BF(1)符號相反,如果直接左旋,調整後的樹就不再是二叉排序樹了。因此,正確做法是先在step1中調整符號,然後才能在step2中進行平衡操作。
由此,可以總結出平衡操作中非常必要的符號統一操作:
最小不平衡子樹的BF和它的子樹的BF符號相反時,就需要對結點先進行一次旋轉使得符號相同,再
反向旋轉一次
才能夠完成平衡操作。
[左旋代碼實現]
這部分代碼最好在紙上自己畫左旋圖更容易理解
/* 對以P爲根的二叉排序樹左旋操作 */
void Left_Rotate(BiTree *P)
{
BiTree R;
R=(*P)->rchild; /* R指向P的右子樹根結點 */
(*P)->rchilde=R->lchild; /* R的左子樹掛接爲P的右子樹 */
R->lchild=(*P);
*P=R; /* P指向新的根結點 */
}
[右旋代碼實現]
這部分代碼最好在紙上自己畫右旋圖更容易理解
/* 對以P爲根的二叉排序樹右旋操作 */
void Right_Rotate(BiTree *P)
{
BiTree L;
L=(*P)->lchild; /* L指向P的左子樹根結點 */
(*P)->lchilde=L->rchild; /* R的右子樹掛接爲P的左子樹 */
L->rchild=(*P);
*P=L; /* P指向新的根結點 */
}
AVL樹的左旋平衡、右旋平衡
AVL樹要在旋轉前要處理符號統一,這一步驟簡稱爲 左平衡旋轉
和 右平衡旋轉
。
[左平衡旋轉處理代碼]
#define LH +1 /* BF左高 */
#define EH 0 /* BF等高 */
#define RH -1 /* BF右高 */
/* 對以指針所指結點爲根的二叉樹做左平衡旋轉處理 */
/* 算法結束時,指針T指向新的根結點*/
void LeftBalance(BiTree *T)
{
BiTree L,Lr;
L=(*T)->lchild; /* L指向T的左子樹根結點 */
switch(L->bf)
{
/* 檢查T的左子樹的平衡度,並做平衡處理 */
case LH:/* 新結點插入在T的左孩子的左子樹上,要作單右旋處理 */
(*T)->bf=L->bf=EH;
Right_Rotate(T);
break;
case RH:/* 新結點插入在T的左孩子的右子樹上,要雙旋處理*/
Lr=L->rchild; /* Lr指向T的左孩子的右子樹根 */
switch(Lr->bf) /* 統一符號,修正T及其左孩子的平衡因子*/
{
case LH:
(*T)->bf=RH;
L->bf=EH;
break;
case EH:
(*T)->bf=L->bf=EH;
break;
case RH:
(*T)->bf=EH;
L->bf=LH;
break;
}
Lr->bf=EH;
Left_Rotate(&(*T)->lchild); /* 對T的左子樹作左旋平衡處理 */
Right_Rotate(T); /* 對T作右旋平衡處理*/
}
}
右旋平衡的函數與左旋平衡的函數一樣,都是對插入新結點後,判斷是否需要做符號統一從而作雙旋操作。
AVL樹的實現算法
[主函數]
/* 若在平衡的二叉排序樹T中不存在和e有相同關鍵字的結點,則插入一個 */
/* 數據元素爲e的新結點並返回1,否則返回0。若因插入而使二叉樹失去平衡,則做平衡旋轉處理,taller反應T是否長高*/
Status InsertAVL(BiTree *T,int e,Status *taller)
{
if(!*T)
{
/* 插入新結點,樹"長高",taller爲TRUE */
*T=(BiTree)malloc(sizeof(BiTNode));
(*T)->data=e;
(*T)->lchild=(*T)->rchild=NULL;
(*T)->bf=EH;
*taller=TRUE;
}
else
{
if(e==(*T)->data)
{ /* 樹中已存在和e有相同關鍵字的結點則不再輸入 */
*taller=FALSE;
return FALSE;
}
if(e<(*T)->data)
{ /* 在左子樹中進行搜索 */
if(!InsertAVL(&(*T)->lchild,e,taller))/* 未插入 */
return FALSE;
if(*taller) /* 已插入到T的左子樹中且左子樹長高*/
{
switch((*T)->bf) /* 檢查T的平衡度 */
{
case LH: /* 左子樹高,左平衡處理 */
LeftBalance(T);
*taller=FALSE;
break;
case EH: /* 原本左右子樹等高,現因左子樹增高而樹增高 */
(*T)->BF=LH;
*taller=TRUE;
break;
case RH: /* 原本右子樹比左子樹高,現等高 */
(*T)->bf=EH;
*taller=FALSE;
break;
}
}
}
else
{/* 繼續在T的右子樹中進行搜索 */
if(!InsertAVL(&(*T)->rchild,e,taller))/* 未插入 */
return FALSE;
if(*taller) /* 已插入到Td 餓右子樹且右子樹"長高" */
{
switch((*T)->bf) /* 檢查T的平衡度 */
{
case LH: /* 原本左子樹比右子樹高,現在左右子樹等高 */
(*T)->bf=EH;
*taller=FALSE;
break;
case EH: /* 原本左右子樹等高,現因右子樹增高而樹增高 */
(*T)->bf=RH;
*taller=TRUE;
break;
case RH: /*原本右子樹比左子樹高,需要做右平衡處理 */
RightBalance(T);
*taller=FALSE;
break;
}
}
}
}
return TRUE;
}
代碼內容比較多,核心在於對插入結點時,分配進入左右子樹,同時左旋平衡或右旋平衡並調整相應結點的bf。
至此,AVL樹的內容基本都囊括進去了,我們可以看到AVL樹每一步都要平衡,平衡因子不大於1。這種平衡是非常嚴格的平衡,還有其他形式的平衡,如多路查找樹 (B樹、B+樹)
和 紅黑樹
。
紅黑樹
[不同方式的平衡]
平衡方式不只有AVL樹這種極端平衡的情況,還有其他的拓展平衡方式。
多路查找樹
多路查找樹包括B樹和拓展的B+樹,和二叉排序樹每個結點只能存儲一個元素,每個結點的孩子數不多於兩個的性質不一樣的是, 多路查找樹每一個結點的孩子數可以多於兩個,每一個結點處都可以存儲多個元素
。
比如最簡單的2-3樹就是這樣一棵多路查找樹:每一個結點都具有兩個孩子(稱爲2結點)或三個孩子(稱爲3結點)。需要注意的是:
- 一個2結點要麼沒有孩子,要麼就要有兩個孩子,不能只有一個孩子。
- 一個3結點包含一大一小兩個元素,要麼沒有孩子,要麼就要有三個孩子,不能只有一個或兩個孩子。
來看一下一個典型的2-3樹是什麼樣子的:
2-3樹的插入刪除可以想象的到:涉及的操作有結點的分裂、合併、補位,這裏不做過多講解。
多路查找樹的用途
2-3只是多路查找樹的簡單特例,2-3樹是3階的B樹,在B樹上查找的過程是一個 順時針查找結點和在結點中查找關鍵字的交叉過程
。
現在來說說B樹的用途,B樹的數據結構就是爲內外村的數據交互準備的。
外存(如硬盤)是將 所有的信息分割成相等大小的頁面,每次硬盤讀寫的都是一個或多個完整的頁面
。如果要處理的硬盤數據量很大,無法一次全部裝入內存中,就要對B樹進行調整,是的B樹的階數(或結點的元素)與硬盤存儲的頁面大小相匹配。比如一棵B樹的階爲1001(1個結點可以包含1000個元素),高度爲2,它可以存儲超過10億(1000X1000X1000)個關鍵字,我們只要讓根結點持久的保留在內存中,那麼在這棵樹上,尋找某一個關鍵字至多需要兩次硬盤的讀取。通過這種方式,在有限內存的情況下,每一次磁盤的訪問都可以獲得最大數量的數據。
而B+樹更是在B樹的基礎上,加上了在葉子結點的新的元素組織方式,將葉子結點鏈接在一起。即 出現在分支結點中的元素會被當作他們的該分支結點位置的中序後繼者(葉子結點)中再次列出。
另外,每一個葉子結點都會保存一個指向後一葉子結點的指針
。
如圖,B+樹中的根結點元素4、6、9都被保留到葉子結點中,葉子結點也保留指向葉子結點的指針。
紅黑樹的定義
紅黑樹是一棵二叉排序樹,它在每個結點上增加了一個存儲位來表示結點的顏色,可以是RED或BLACK。通過對任一條從根到葉子的簡單路徑上各個結點的顏色進行約束,紅黑樹確保沒有一條路徑會比其它路徑長2倍,因此是近乎平衡的。
樹中的結點包含5個屬性:color、key、left、right和p。如果一個結點沒有子結點或父結點,則該結點相應指針屬性值爲NIL。
上面提到的這些都是爲了讓大家有個直觀的感受,在具體的操作中,我們肯定是要在插入或者刪除結點的過程中時刻保持着紅黑樹的性質。
一棵紅黑樹是具有如下性質的二叉排序樹:
- 每個結點的顏色只能是紅色或黑色的。
- 根結點是黑色的。
- 每個葉子結點(NIL)是黑色的。
- 如果一個結點是紅色的,那麼它的兩個子結點都是黑色的。
- 對每個結點,從該結點到其所有後代葉子簡單的簡單路徑上,均包含相同數目的黑色結點。
來看一個最典型的紅黑樹,以爲葉子結點都是黑色的,所以統一出來當成NIL結點。紅色結點表示RED,灰色結點表示BLACK。
紅黑樹的旋轉
爲了維護上述紅黑樹的性質,必須調整結點的顏色和指針結構。
指針結構的修改是通過旋轉完成的。這是一種能保持二叉排序樹性質的局部調整操作。這裏的左旋右旋操作和AVL樹中的左旋右旋一樣,具體代碼可回到AVL樹部分去查看。
紅黑樹的插入
將新結點z插入到樹T中,然後將z塗成紅色,並調用 旋轉着色RB_INSERT_FIXUP函數
(在AVL樹中是旋轉平衡)來把插入新結點後的樹調整爲紅黑樹。
RB_INSERT(T,z)
y=T.nil;
x=T.root
while x≠T.nil
y=x;
if z.key<x.key
x=x.left
else
x=x.right
z.p=y
if y==T.nil
T.root=z
elseif z.key<y.key
y.left=z
else y.right=z
z.left=T.nil
Z.right=T.nil
z.color=RED
RB_INSERT_FIXUP(T,z)
RB_INSERT_FIXUP(T,z)
wile z.p.color==RED
if z.p==z.p.p.left
y=z.p.p.right
if y.color==RED //case 1
z.p.color =BLACK
y.color=BLACK
z.p.p.color=RED
z=z.p.p
else if z==z.p.right //case 2
z=z.p
LEFT_ROTATE(T,z)
else //case 3
z.p.color=BLACK
z.p.p.color=RED
RIGHT_ROTATE(T,z.p.p)
else(same as then clause
with "right" and "left" exchanged)
T.root.color=BLACK
根據這個紅黑樹插入新結點的情況,我們來過一下代碼流程。
(a)插入結點z。由於z和它的副結點z.p都是紅色的,違反了性質4(紅結點的後代要是黑結點)。由於z的叔結點y也是紅色的,適用於case1。結點被重新着色,並且指針z沿樹上升,如(b)所示。再一次z及其父結點都是紅色的,但z的叔結點y是黑色的。因爲z是z.p的右孩子,可以應用case2。在執行一次左旋後,所得結果樹爲(c)。現在,z是其父結點的左孩子,可以應用case3。重新着色並執行一次右旋後得(d)中的樹,它是一棵紅黑樹。
紅黑樹的刪除
從一棵紅黑樹中刪除結點的過程需要調用子過程RB_TRANSPLANT。
RB_TRANSPLANT(T,u,v)
if u.p==T.nil
T.root=v
elseif u==u.p.left
u.p.left=v
else u.p.right=v
v.p=u.p
紅黑樹的刪除和普通的二叉樹刪除一樣,只是需要對結點的顏色添加判斷,需要用更多的代碼來記錄結點y的蹤跡,y有可能導致紅黑性質的破壞。當想要刪除結點z,且此事z的子結點少於2個時,z從樹中刪除,並讓y成爲z。當z有兩個子結點時,y應該是z的後繼,並且y將移至樹中的z位置。在結點被移除或者在樹中移動之前,必須記住y的顏色,並且紀錄結點x的蹤跡,將x移至樹中y的原來位置,因爲結點x也可能引起紅黑性質的破壞。刪除結點z之後,RB_DELETE調用一個輔助過程RB_DELETE_FIXUP,該過程通過改變顏色和旋轉來恢復紅黑性質。
RB_DELETE(T,z)
y=z
y-original-color=y.color
if z.left=T.nil
x=z.right
RB_TRANSPLANT(T,z,z.right)
elseif z.right==T.nil
x=z.left
RB_TRANSPLANT(T,z,z.left)
else y=TREE-MINMUM(z.right)
y-orginal-color=y.color
x=y.right
if y.p==z
x.p=y
else RB_TRANSPLANT(T,z,y)
y.left=z.left
y.left.p=y
y.color=z.color
if y-original-color==BLACK
RB_DELETE_FIXUP(T,x)
上面的代碼是跟隨結點y的移動追蹤過程。如果結點y是黑色的,紅黑樹性質遭到破壞,需要調用RB_DELETE_FIXUP進行補救。
RB_DELETE_FIXUP(T,x)
while x≠T.root and x.color==BLACK
if x==x.p.left
w=x.p.right
if w.color==RED //case 1
w.color=BLACK
x.p.color=RED
LEFT_ROTATE(T,x.p)
w=x.p.right
if w.left.color==BLACK and w.right.color==BLACK //case 2
w.color=RED
x=x.p
else if w.right.color==BLACK //case 3
w.left.color=BLACK
w.color=RED
RIGHT_ROTATE(T,w)
w=x.p.right
w.color=x.p.color //case 4
x.p.color=BLACK
w.right.color=BLACK
LEFT_ROTATE(T,x.p)
x=T.root
else (same as then clause with "right" and "left" exchanged)
x.color=BLACK
while循環的目標是將額外的黑色沿樹上移,直到:
- x指向紅黑結點,將x着色爲黑色
- x指向根結點,可以簡單的“移除”額外的黑色
- 執行適當的旋轉和重新着色,退出循環
分析下面幾個狀態:紅色結點表示RED,灰色結點表示BLACK,棕紅色結點表示RED或BLACK,用c和c’表示。
(a)通過結點B和D顏色交換和執行左旋,可將case 1轉化爲case 2;
(b)在case 2中,將結點D着爲紅色,並將x設爲指向結點B後,由指針x所表示的額外黑色沿樹上升。如果通過case 1進入case 2,則while循環結束,因爲新的結點x是紅黑的,因此其color屬性c是RED.
(c)通過結點C和D交換顏色並執行一次右旋,可以將case 3轉換成case 4。
(d)case 4中,通過改變某些結點的顏色並執行一次左旋,可以將由x表示的額外黑色去掉,終止循環。
這是最正宗的紅黑樹理解方式,還有一種方式是通過2-3樹分裂操作替換成顏色操作,具體請看博文查找(一)史上最簡單清晰的紅黑樹講解
總結
作爲平衡二叉排序樹衆多的實現之一,紅黑樹特別的 引入顏色
這一個約束條件保持樹的平衡。通過旋轉可以降低樹的高度並轉換顏色。
紅黑樹的插入和刪除操作是比較難理解的:它們都是建立在紅黑樹是平衡的情況下,加入一個結點或者刪除一個結點,影響了平衡,就需要向兄弟結點、父結點、叔結點進行借調和顏色互換,這一操作是通過旋轉實現的。但是換完以後,是否還是平衡的呢,這是不一定的,需要從被借調結點出發開始向上調整,直到整個樹都是平衡的。修復過程中,插入具體分3種情況,刪除分4中情況。
紅黑樹和AVL樹都是從樹的平衡性出發,找到合適的平衡方式,一個通過顏色標識限定,一個通過樹高差限定,使樹都處於平衡狀態,提高了算法的實用性和效率。
參考資料
- 算法導論(第三版)
- 大話數據結構
- 數據結構(嚴蔚敏)
- 紅黑樹深入剖析及Java實現(美團點評技術團隊)
附錄
紅黑樹的java實現:
作者:美團點評技術團隊
鏈接:https://zhuanlan.zhihu.com/p/24367771
來源:知乎
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
public class RBTreeNode<T extends Comparable<T>> {
private T value;//node value
private RBTreeNode<T> left;//left child pointer
private RBTreeNode<T> right;//right child pointer
private RBTreeNode<T> parent;//parent pointer
private boolean red;//color is red or not red
public RBTreeNode(){}
public RBTreeNode(T value){this.value=value;}
public RBTreeNode(T value,boolean isRed){this.value=value;this.red = isRed;}
public T getValue() {
return value;
}
void setValue(T value) {
this.value = value;
}
RBTreeNode<T> getLeft() {
return left;
}
void setLeft(RBTreeNode<T> left) {
this.left = left;
}
RBTreeNode<T> getRight() {
return right;
}
void setRight(RBTreeNode<T> right) {
this.right = right;
}
RBTreeNode<T> getParent() {
return parent;
}
void setParent(RBTreeNode<T> parent) {
this.parent = parent;
}
boolean isRed() {
return red;
}
boolean isBlack(){
return !red;
}
/**
* is leaf node
**/
boolean isLeaf(){
return left==null && right==null;
}
void setRed(boolean red) {
this.red = red;
}
void makeRed(){
red=true;
}
void makeBlack(){
red=false;
}
@Override
public String toString(){
return value.toString();
}
}
public class RBTree<T extends Comparable<T>> {
private final RBTreeNode<T> root;
//node number
private java.util.concurrent.atomic.AtomicLong size =
new java.util.concurrent.atomic.AtomicLong(0);
//in overwrite mode,all node's value can not has same value
//in non-overwrite mode,node can have same value, suggest don't use non-overwrite mode.
private volatile boolean overrideMode=true;
public RBTree(){
this.root = new RBTreeNode<T>();
}
public RBTree(boolean overrideMode){
this();
this.overrideMode=overrideMode;
}
public boolean isOverrideMode() {
return overrideMode;
}
public void setOverrideMode(boolean overrideMode) {
this.overrideMode = overrideMode;
}
/**
* number of tree number
* @return
*/
public long getSize() {
return size.get();
}
/**
* get the root node
* @return
*/
private RBTreeNode<T> getRoot(){
return root.getLeft();
}
/**
* add value to a new node,if this value exist in this tree,
* if value exist,it will return the exist value.otherwise return null
* if override mode is true,if value exist in the tree,
* it will override the old value in the tree
*
* @param value
* @return
*/
public T addNode(T value){
RBTreeNode<T> t = new RBTreeNode<T>(value);
return addNode(t);
}
/**
* find the value by give value(include key,key used for search,
* other field is not used,@see compare method).if this value not exist return null
* @param value
* @return
*/
public T find(T value){
RBTreeNode<T> dataRoot = getRoot();
while(dataRoot!=null){
int cmp = dataRoot.getValue().compareTo(value);
if(cmp<0){
dataRoot = dataRoot.getRight();
}else if(cmp>0){
dataRoot = dataRoot.getLeft();
}else{
return dataRoot.getValue();
}
}
return null;
}
/**
* remove the node by give value,if this value not exists in tree return null
* @param value include search key
* @return the value contain in the removed node
*/
public T remove(T value){
RBTreeNode<T> dataRoot = getRoot();
RBTreeNode<T> parent = root;
while(dataRoot!=null){
int cmp = dataRoot.getValue().compareTo(value);
if(cmp<0){
parent = dataRoot;
dataRoot = dataRoot.getRight();
}else if(cmp>0){
parent = dataRoot;
dataRoot = dataRoot.getLeft();
}else{
if(dataRoot.getRight()!=null){
RBTreeNode<T> min = removeMin(dataRoot.getRight());
//x used for fix color balance
RBTreeNode<T> x = min.getRight()==null ? min.getParent() : min.getRight();
boolean isParent = min.getRight()==null;
min.setLeft(dataRoot.getLeft());
setParent(dataRoot.getLeft(),min);
if(parent.getLeft()==dataRoot){
parent.setLeft(min);
}else{
parent.setRight(min);
}
setParent(min,parent);
boolean curMinIsBlack = min.isBlack();
//inherit dataRoot's color
min.setRed(dataRoot.isRed());
if(min!=dataRoot.getRight()){
min.setRight(dataRoot.getRight());
setParent(dataRoot.getRight(),min);
}
//remove a black node,need fix color
if(curMinIsBlack){
if(min!=dataRoot.getRight()){
fixRemove(x,isParent);
}else if(min.getRight()!=null){
fixRemove(min.getRight(),false);
}else{
fixRemove(min,true);
}
}
}else{
setParent(dataRoot.getLeft(),parent);
if(parent.getLeft()==dataRoot){
parent.setLeft(dataRoot.getLeft());
}else{
parent.setRight(dataRoot.getLeft());
}
//current node is black and tree is not empty
if(dataRoot.isBlack() && !(root.getLeft()==null)){
RBTreeNode<T> x = dataRoot.getLeft()==null
? parent :dataRoot.getLeft();
boolean isParent = dataRoot.getLeft()==null;
fixRemove(x,isParent);
}
}
setParent(dataRoot,null);
dataRoot.setLeft(null);
dataRoot.setRight(null);
if(getRoot()!=null){
getRoot().setRed(false);
getRoot().setParent(null);
}
size.decrementAndGet();
return dataRoot.getValue();
}
}
return null;
}
/**
* fix remove action
* @param node
* @param isParent
*/
private void fixRemove(RBTreeNode<T> node,boolean isParent){
RBTreeNode<T> cur = isParent ? null : node;
boolean isRed = isParent ? false : node.isRed();
RBTreeNode<T> parent = isParent ? node : node.getParent();
while(!isRed && !isRoot(cur)){
RBTreeNode<T> sibling = getSibling(cur,parent);
//sibling is not null,due to before remove tree color is balance
//if cur is a left node
boolean isLeft = parent.getRight()==sibling;
if(sibling.isRed() && !isLeft){//case 1
//cur in right
parent.makeRed();
sibling.makeBlack();
rotateRight(parent);
}else if(sibling.isRed() && isLeft){
//cur in left
parent.makeRed();
sibling.makeBlack();
rotateLeft(parent);
}else if(isBlack(sibling.getLeft()) && isBlack(sibling.getRight())){//case 2
sibling.makeRed();
cur = parent;
isRed = cur.isRed();
parent=parent.getParent();
}else if(isLeft && !isBlack(sibling.getLeft())
&& isBlack(sibling.getRight())){//case 3
sibling.makeRed();
sibling.getLeft().makeBlack();
rotateRight(sibling);
}else if(!isLeft && !isBlack(sibling.getRight())
&& isBlack(sibling.getLeft()) ){
sibling.makeRed();
sibling.getRight().makeBlack();
rotateLeft(sibling);
}else if(isLeft && !isBlack(sibling.getRight())){//case 4
sibling.setRed(parent.isRed());
parent.makeBlack();
sibling.getRight().makeBlack();
rotateLeft(parent);
cur=getRoot();
}else if(!isLeft && !isBlack(sibling.getLeft())){
sibling.setRed(parent.isRed());
parent.makeBlack();
sibling.getLeft().makeBlack();
rotateRight(parent);
cur=getRoot();
}
}
if(isRed){
cur.makeBlack();
}
if(getRoot()!=null){
getRoot().setRed(false);
getRoot().setParent(null);
}
}
//get sibling node
private RBTreeNode<T> getSibling(RBTreeNode<T> node,RBTreeNode<T> parent){
parent = node==null ? parent : node.getParent();
if(node==null){
return parent.getLeft()==null ? parent.getRight() : parent.getLeft();
}
if(node==parent.getLeft()){
return parent.getRight();
}else{
return parent.getLeft();
}
}
private boolean isBlack(RBTreeNode<T> node){
return node==null || node.isBlack();
}
private boolean isRoot(RBTreeNode<T> node){
return root.getLeft() == node && node.getParent()==null;
}
/**
* find the successor node
* @param node current node's right node
* @return
*/
private RBTreeNode<T> removeMin(RBTreeNode<T> node){
//find the min node
RBTreeNode<T> parent = node;
while(node!=null && node.getLeft()!=null){
parent = node;
node = node.getLeft();
}
//remove min node
if(parent==node){
return node;
}
parent.setLeft(node.getRight());
setParent(node.getRight(),parent);
//don't remove right pointer,it is used for fixed color balance
//node.setRight(null);
return node;
}
private T addNode(RBTreeNode<T> node){
node.setLeft(null);
node.setRight(null);
node.setRed(true);
setParent(node,null);
if(root.getLeft()==null){
root.setLeft(node);
//root node is black
node.setRed(false);
size.incrementAndGet();
}else{
RBTreeNode<T> x = findParentNode(node);
int cmp = x.getValue().compareTo(node.getValue());
if(this.overrideMode && cmp==0){
T v = x.getValue();
x.setValue(node.getValue());
return v;
}else if(cmp==0){
//value exists,ignore this node
return x.getValue();
}
setParent(node,x);
if(cmp>0){
x.setLeft(node);
}else{
x.setRight(node);
}
fixInsert(node);
size.incrementAndGet();
}
return null;
}
/**
* find the parent node to hold node x,if parent value equals x.value return parent.
* @param x
* @return
*/
private RBTreeNode<T> findParentNode(RBTreeNode<T> x){
RBTreeNode<T> dataRoot = getRoot();
RBTreeNode<T> child = dataRoot;
while(child!=null){
int cmp = child.getValue().compareTo(x.getValue());
if(cmp==0){
return child;
}
if(cmp>0){
dataRoot = child;
child = child.getLeft();
}else if(cmp<0){
dataRoot = child;
child = child.getRight();
}
}
return dataRoot;
}
/**
* red black tree insert fix.
* @param x
*/
private void fixInsert(RBTreeNode<T> x){
RBTreeNode<T> parent = x.getParent();
while(parent!=null && parent.isRed()){
RBTreeNode<T> uncle = getUncle(x);
if(uncle==null){//need to rotate
RBTreeNode<T> ancestor = parent.getParent();
//ancestor is not null due to before before add,tree color is balance
if(parent == ancestor.getLeft()){
boolean isRight = x == parent.getRight();
if(isRight){
rotateLeft(parent);
}
rotateRight(ancestor);
if(isRight){
x.setRed(false);
parent=null;//end loop
}else{
parent.setRed(false);
}
ancestor.setRed(true);
}else{
boolean isLeft = x == parent.getLeft();
if(isLeft){
rotateRight(parent);
}
rotateLeft(ancestor);
if(isLeft){
x.setRed(false);
parent=null;//end loop
}else{
parent.setRed(false);
}
ancestor.setRed(true);
}
}else{//uncle is red
parent.setRed(false);
uncle.setRed(false);
parent.getParent().setRed(true);
x=parent.getParent();
parent = x.getParent();
}
}
getRoot().makeBlack();
getRoot().setParent(null);
}
/**
* get uncle node
* @param node
* @return
*/
private RBTreeNode<T> getUncle(RBTreeNode<T> node){
RBTreeNode<T> parent = node.getParent();
RBTreeNode<T> ancestor = parent.getParent();
if(ancestor==null){
return null;
}
if(parent == ancestor.getLeft()){
return ancestor.getRight();
}else{
return ancestor.getLeft();
}
}
private void rotateLeft(RBTreeNode<T> node){
RBTreeNode<T> right = node.getRight();
if(right==null){
throw new java.lang.IllegalStateException("right node is null");
}
RBTreeNode<T> parent = node.getParent();
node.setRight(right.getLeft());
setParent(right.getLeft(),node);
right.setLeft(node);
setParent(node,right);
if(parent==null){//node pointer to root
//right raise to root node
root.setLeft(right);
setParent(right,null);
}else{
if(parent.getLeft()==node){
parent.setLeft(right);
}else{
parent.setRight(right);
}
//right.setParent(parent);
setParent(right,parent);
}
}
private void rotateRight(RBTreeNode<T> node){
RBTreeNode<T> left = node.getLeft();
if(left==null){
throw new java.lang.IllegalStateException("left node is null");
}
RBTreeNode<T> parent = node.getParent();
node.setLeft(left.getRight());
setParent(left.getRight(),node);
left.setRight(node);
setParent(node,left);
if(parent==null){
root.setLeft(left);
setParent(left,null);
}else{
if(parent.getLeft()==node){
parent.setLeft(left);
}else{
parent.setRight(left);
}
setParent(left,parent);
}
}
private void setParent(RBTreeNode<T> node,RBTreeNode<T> parent){
if(node!=null){
node.setParent(parent);
if(parent==root){
node.setParent(null);
}
}
}
/**
* debug method,it used print the given node and its children nodes,
* every layer output in one line
* @param root
*/
public void printTree(RBTreeNode<T> root){
java.util.LinkedList<RBTreeNode<T>> queue =new java.util.LinkedList<RBTreeNode<T>>();
java.util.LinkedList<RBTreeNode<T>> queue2 =new java.util.LinkedList<RBTreeNode<T>>();
if(root==null){
return ;
}
queue.add(root);
boolean firstQueue = true;
while(!queue.isEmpty() || !queue2.isEmpty()){
java.util.LinkedList<RBTreeNode<T>> q = firstQueue ? queue : queue2;
RBTreeNode<T> n = q.poll();
if(n!=null){
String pos = n.getParent()==null ? "" : ( n == n.getParent().getLeft()
? " LE" : " RI");
String pstr = n.getParent()==null ? "" : n.getParent().toString();
String cstr = n.isRed()?"R":"B";
cstr = n.getParent()==null ? cstr : cstr+" ";
System.out.print(n+"("+(cstr)+pstr+(pos)+")"+"\t");
if(n.getLeft()!=null){
(firstQueue ? queue2 : queue).add(n.getLeft());
}
if(n.getRight()!=null){
(firstQueue ? queue2 : queue).add(n.getRight());
}
}else{
System.out.println();
firstQueue = !firstQueue;
}
}
}
public static void main(String[] args) {
RBTree<String> bst = new RBTree<String>();
bst.addNode("d");
bst.addNode("d");
bst.addNode("c");
bst.addNode("c");
bst.addNode("b");
bst.addNode("f");
bst.addNode("a");
bst.addNode("e");
bst.addNode("g");
bst.addNode("h");
bst.remove("c");
bst.printTree(bst.getRoot());
}
}