終於深入的理解並用代碼一步一步實現了紅黑樹。Linux內核的進程調度和內存管理,STL庫的map容器的實現都使用了紅黑樹,這是一種效率很高的二叉搜索樹,效率基本上滿足log(n),它與AVL樹有什麼區別呢?
學習了AVL樹之後,如果你覺得關於二叉搜索樹的學習就結束了,那你就錯了,因爲,還有一個樹,紅黑樹,紅黑樹學習完之後,二叉搜索樹基本上就告一段落了,還有一些二叉搜索樹的變種,如AA-Tree,在這裏就不講了,弄明白紅黑樹,學習AA樹應該也不是太難的事情了。
可能有人要問,有了AVL樹,幹嘛還要紅黑樹呢?是啊,AVL樹效率是最穩定最高的二叉搜索樹,因爲AVL是一顆高度平衡的二叉樹,任意一個結點的左右子節點高度差不大於1,有人問,有沒有高度差爲0的,那我想請問你,給你兩個點,你給我造出來一個高度差爲0的樹出來!!!
閒話不多扯,還是說說AVL樹和RB-Tree的故事。AVL樹是高度平衡,爲了達到高度平衡,在建樹和刪除的時候操作較爲複雜,並不是說變化的算法複雜,而是牽扯的結點較多,有時候爲了在插入一個結點以後能滿足AVL樹的性質,整棵樹都會跟着旋轉變換。插入和刪除的效率較低,而RB-Tree就是爲了在提高插入刪除效率的同時,又不會使查找效率有大的變化而誕生的。紅黑樹不要求高度平衡,但是其也是一顆泛平衡樹,因爲從根節點出發,到葉子節點的最長距離不會超過最短距離的二倍,從下面紅黑樹的性質就可以看出來:
1.Anode is either red or black.
一個結點非黑即紅。
2.Theroot is black.
根節點是黑色
3.Allleaves (NIL) are black.
所有葉子節點是黑色
4.Ifa node is red, then both its children are black.
如果一個結點是紅色,則它的孩子節點必須是黑色。
5.Everypath from a given node to any of its descendant NIL nodes contains the samenumber of black nodes.The uniform number of blacknodes in the paths from root to leaves is called the black-height of thered–black tree.
黑高相同。
所謂的黑高,就是指從一個節點出發,到葉子節點(NIL)所經歷的黑色節點的個數
如下圖所示
這裏我們引入了一個NIL節點,這個NIL結點的顏色是黑色的,它並不是數據,而是作爲類似於標記用的,說明已經到底了…,爲什麼引入這個NIL節點?是爲了簡化刪除中的調整代碼,在刪除的調整函數void deleteadjust(Node** root, Node* node)的註釋中會有說明。
紅黑樹的難點在於插入和刪除的調整上,插入和刪除節點以後都要保證不會破壞上述五點紅黑樹特性。插入操作比較簡單一些,刪除操作比較複雜一點,但是,操作的思路還是比較清晰的,下面我們就結合圖例還說明插入和刪除的各種case,以及對應的操作方法。
插入:
紅黑樹規定:待插入的結點的顏色都要定義成紅色。
插入操作我們分爲兩部分,一部分是插入,另一部分是插入後的調整。插入操作和前面BST樹以及AVL樹的插入一樣。插入完畢後,有可能遇到以下幾種情況:
Case 1:
上圖中N是插入的結點,可以看到,N和其父節點P都是紅色,而且N的叔結點U也是紅色,這就違背了性質4(如果一個結點是紅色,則它的孩子節點必須是黑色),調整的操作方法是:將P和U都變爲黑色,G變爲紅色,這樣就滿足了性質4,也不會違背性質5,完美結束。
Case 2:
N結點的叔結點U如果是黑節點,操作:G改變爲紅色,P改變爲黑色,在P出一次右旋,就OK了。
Case 3:
圖3
可以看到,圖2和圖1有點像,只要對P進行一次左旋,左旋之後就變成了case2的情況,按照case的操作,就OK了。
後面的插入操作的代碼就可以照着上圖進行理解,因爲插入操作比較簡單,因此沒有過多的說明。本文的重點放在了刪除操作上,插入操作還可以結合以下兩個鏈接的內容來理解:
https://en.wikipedia.org/wiki/Red%E2%80%93black_tree
http://www.cnblogs.com/fornever/archive/2011/12/02/2270692.html(可以理解是維基百科的中文翻譯,英文好的同學就不必看了)
刪除:
下面,我們就重點說說紅黑樹的刪除操作,
和BST以及AVL樹的刪除操作一樣,其實是找到要刪除節點的直接後繼(右子樹的最左子節點)或者直接前驅節點(左子樹的最右子節點),然後用直接後繼(或者直接前驅,後面我們都以直接後繼來描述)的值替換到要刪除的結點的值,然後刪除直接後繼節點。
如果這個直接後繼節點是紅色,而且孩子節點都是NIL,那就省事了,直接刪除就行了,因爲刪除這個紅節點不會破壞任何一個性質。
如果這個直接後繼節點N是黑色,而他的右孩子SR是紅色,那麼SR的兩個孩子肯定是NIL,因爲SR本身是紅色,隨意本身不可能跟紅色節點,而N的左節點是nil,這樣就是的SR不能有黑色節點,否則就會使得N的左右子樹的黑高不一樣。調整操作是把SR染成黑色,替換掉N節點即可。
上面我們討論了兩種比較簡單的情況,那麼我們處理一下負責的情況,那就是如果N是一個黑色的結點,而且N的兩個孩子都是NIL節點,那麼刪除了N以後就會破壞平衡,如下圖。
刪除了N以後,P的左子樹的黑高是少1的,這是就需要調平,當然,P的右子節點不一定是黑色的,這裏只是畫出一種情況進行說明。後面涉及到的各種case,其實就是P和其右子節點顏色各種不同造成的,下面就一一分析。
case 1:
case 2:
case 3:
case 4:
case 5:
case 5的圖只畫出了P節點右子樹,N節點是P的左子樹,沒有畫,請自行腦補即可。
以上刪除操作都是討論的N是P的左子節點,S是P的右子節點的情況,至於N是P右子節點,S是P左子節點的情況,操作一樣,只是左旋變右旋,右旋變左旋以及操作的子節點不一樣,操作方法一樣。
上面五種case就是能遇到的情況。這裏說一下爲什麼增加NIL這個節點,設想一下,如果沒有NIL這個節點而是用NULL,那麼我們在第一次進行調整的時候就得作爲特殊情況考慮,因爲NULL是沒有color的,然後才能用通用的調整代碼,這裏我們引入了NIL之後就好多了,NIL本身無數據,但是其有顏色,所以能將第一次調整操作也融入到通用代碼中了。
好了,下面我們就把代碼貼上:
/*RB-Tree.c*/
#include "RB-Tree.h"
static Node* NIL = NULL;
/*get grandparent*/
Node* grandparent(Node* node)
{
return node->pp->pp;
}
/*get parent*/
Node* parent(Node* node)
{
return node->pp;
}
/*get uncle*/
Node* uncle(Node* node)
{
if (grandparent(node) == NULL) {
return NULL;
}
return (parent(node) == grandparent(node)->plc) ? grandparent(node)->prc : grandparent(node)->plc;
}
Node* createnode(int key,int val)
{
Node* node = (Node*)malloc(sizeof(Node));
node->cnt = 0;
color = RED;
node->color = color;
node->key = key;
node->val = val;
node->plc = NIL;
node->prc = NIL;
node->pp = NIL;
return node;
}
/*左旋轉*/
Node* Left_Rotate(Node** root, Node* node)
{
Node* tmp = node->prc;
node->prc = tmp->plc;
if (tmp->plc != NULL) {
tmp->plc->pp = node;
}
tmp->pp = parent(node);
if (parent(node) == NULL) {
*root = tmp;
}
else {
if (parent(node)->plc == node) {
parent(node)->plc = tmp;
}
else {
parent(node)->prc = tmp;
}
}
node->pp = tmp;
tmp->plc = node;
return tmp;
}
/*右旋轉*/
Node* Right_Rotate(Node** root, Node* node)
{
Node* tmp = node->plc;
node->plc = tmp->prc;
if (tmp->prc != NULL) {
tmp->prc->pp = node;
}
tmp->pp = node->pp;
if (parent(node) == NULL) {
*root = tmp;
}
else {
if (node->pp->plc = node) {
node->pp->plc = tmp;
}
else {
node->pp->prc = tmp;
}
}
tmp->prc = node;
node->pp = tmp;
return tmp;
}
/*插入過程*/
RESULT _insert_(Node** root, Node* node)
{
Node* cur = *root;
Node* tmp = NULL;
while (cur != NIL)
{
tmp = cur;
if ( node->val > cur->val ) {
cur = cur->prc;
}
else if (node->val < tmp->val) {
cur = cur->plc;
}
else {
cur->cnt++;
return 2;
}
}
node->pp = tmp;
if (node->key < tmp->key) {
tmp->plc = node;
}
else {
tmp->prc = node;
}
return SUCCESS;
}
/*插入節點後的調整函數*/
void insertadjust(Node**root,Node* node)
{
while (parent(node)->color == RED)
{
/*這裏分兩種情況,叔結點是紅色,叔結點不是紅色(叔結點是NULL或者叔結點是黑色)*/
/*叔結點是紅色*/
if (uncle(node) != NIL && uncle(node)->color == RED) {
parent(node)->color = BLACK;
uncle(node)->color = BLACK;
grandparent(node)->color = RED;
node = grandparent(node);
if (parent(node) == NULL)
{
break;
}
}
/*叔結點是空或者叔結點是黑色*/
else {
if (parent(node) == grandparent(node)->plc) {
if (node == parent(node)->prc) {
/*左旋*/
node = Left_Rotate(root, parent(node))->plc;
}
parent(node)->color = BLACK;
grandparent(node)->color = RED;
/*右旋*/
Right_Rotate(root, grandparent(node));
}
else {
if (node == parent(node)->plc) {
/*右旋*/
node = Right_Rotate(root, parent(node))->prc;
}
parent(node)->color = BLACK;
grandparent(node)->color = RED;
/*左旋*/
Left_Rotate(root, grandparent(node));
}
break;
}
}
}
/*插入節點,並進行必要的調整*/
RESULT insert(Node** root, int key, int val)
{
Node* node = NULL;
if (*root == NULL) {
NIL = (Node*)malloc(sizeof(Node));
*root = (Node*)malloc(sizeof(Node));
NIL->color = BLACK;
(*root)->color = BLACK;
(*root)->plc = NIL;
(*root)->prc = NIL;
(*root)->pp = NULL;
(*root)->key = key;
(*root)->val = val;
(*root)->cnt = 1;
return SUCCESS;
}
else
{
node = createnode(key, val);
if (!node) {
perror("malloc failure!!!\n");
return FAILURE;
}
}
/*插入節點*/
if (2 == _insert_(root, node)) /*返回2說明插入的值已經存在,只需要計數變量+1即可*/ {
return SUCCESS;
}
/*父節點如果是黑色,就不會衝突,不會違背任何性質,如果父節點是紅色,則需要調整*/
insertadjust(root, node);
(*root)->color = BLACK;
return SUCCESS;
}
Node* search(Node * root, int key)
{
Node* node = root;
while (node != NULL)
{
if (key > node->key) {
node = node->prc;
}
else if (key < node->key) {
node = node->plc;
}
else
break;
}
return node;
}
void deleteadjust(Node** root, Node* node)
{
/*nil節點的用處體現在此處,剛進入此函數是,node是nil節點,這樣纔不會報錯,不然如果沒有nil,而是NULL,
此處肯定是要報錯的(node->color),如果不使用nil節點而用null,要想跳過這個錯誤,只能在這個函數外
先進行一次處理,使得node不在是NULL以後才能進入此函數*/
while (node != *root && node->color == BLACK)
{
/*node節點經過迭代之後,就不能確定其實其父節點的左子節點還是右子節點了,因此分兩種情況處理,
兩種情況下,旋轉方向不同,因此其兄弟節點的左右子節點顏色判斷要相反,case 4 和 case 5 體現了這一點*/
if (node == parent(node)->plc) {
Node* Parent = parent(node);
Node* Brother = parent(node)->prc;/*默認取右子樹的最左子節點,所以兄弟節點是右孩子*/
if (Brother->color == RED) {
/*case 1:兄弟節點brother紅色,父節點P,brother的左右孩子(不爲NIL,否則原來就不平衡)一定是黑,
操作:Parent和Brother的顏色互換,在Parent點左旋轉,現在P的左子樹還是比右子樹黑高少1,因此,
將Brother更新一下,在node處重新進行判斷調整*/
Brother->color = BLACK;
Parent->color = RED;
Left_Rotate(root,parent(node));
Brother = Parent->prc;
}
else if (Parent->color == BLACK && Brother->color == BLACK &&
Brother->plc->color == BLACK && Brother->prc->color == BLACK) {
/*case 2:Parent節點是黑色,Brother節點以及Brother節點的左右子節點都是黑色,
操作:將Brother節點變成紅色,在Parent節點處左旋,相當於原來以P節點(現在是以B節點)爲根子節點
的子樹黑高整體減少1,用Brother節點代替原來的node節點,重新進行判斷調整*/
Brother->color = RED;
Left_Rotate(root,Parent);
node = Brother;
continue;
}
else if (Parent->color == RED &&
Brother->plc->color == BLACK && Brother->prc->color == BLACK) {
/*case 3:Parent是紅色,Brother的孩子們都是黑色
操作:將Parent變爲黑色,Brother變爲紅色即可滿足性質*/
Parent->color = BLACK;
Brother->color = RED;
node = *root;
}
else if (Brother->color == BLACK && Brother->prc->color == RED) {
/*case 4:Parent爲任意色,如果Brother的右子節點是紅色,左子節點顏色任意
Brother顏色編程parent的顏色,Parent的顏色變爲黑色,Brother右子節點的顏色改爲黑色*/
Brother->color = Parent->color;
Parent->color = BLACK;
Brother->prc->color = BLACK;
Left_Rotate(root,Parent);
node = *root;
}
else if (Brother->color == BLACK &&
Brother->plc->color == RED && Brother->prc->color == BLACK) {
/*case 5:Parent顏色任意,Brother顏色是黑色,Brother的左子節點是紅色,右孩子是黑色,
操作:B的左子節點變爲黑色,B變爲紅色,然後在B出右旋,處理以後就變成了case 4的情況*/
Brother->plc->color = BLACK;
Brother->color = RED;
Right_Rotate(root,Brother);
Brother = Brother->plc;/*Brother的左子節點代替了原來brother的位置,因此更新Brother的指向*/
/*對於case 5 處理一下之後變成了case 4的情況,在case 4處理跳出*/
}
}
else {
Node* Parent = parent(node);
Node* Brother = parent(node)->plc;/*默認取右子樹的最左子節點,所以兄弟節點是右孩子*/
if (Brother->color == RED) {
/*case 1*/
Brother->color = BLACK;
Parent->color = RED;
Right_Rotate(root, parent(node));
Brother = Parent->plc;
}
else if (Parent->color == BLACK && Brother->color == BLACK &&
Brother->plc->color == BLACK && Brother->prc->color == BLACK) {
/*case 2*/
Brother->color = RED;
Right_Rotate(root, Parent);
node = Brother;
continue;
}
else if (Parent->color == RED &&
Brother->plc->color == BLACK && Brother->prc->color == BLACK) {
/*case 3*/
Parent->color = BLACK;
Brother->color = RED;
node = *root;
}
else if (Brother->color == BLACK && Brother->plc->color == RED) {
/*case 4*/
Brother->color = Parent->color;
Parent->color = BLACK;
Brother->plc->color = BLACK;
Right_Rotate(root, Parent);
node = *root;
}
else if (Brother->color == BLACK &&
Brother->prc->color == RED && Brother->plc->color == BLACK) {
/*case 5*/
Brother->prc->color = BLACK;
Brother->color = RED;
Left_Rotate(root, Brother);
Brother = Brother->prc;
}
}
}
node->color = BLACK;
}
void _delete_(Node** root, Node* node)
{
Node* tmp = NULL;/*實際要被刪除的點*/
if (node->plc == NIL || node->prc == NIL) {
tmp = node;
}
else {
Node* rightsubtree = node->prc;
Node* minright = rightsubtree;
while (rightsubtree->plc != NIL) {
minright = rightsubtree->plc;
rightsubtree = rightsubtree->plc;
}
tmp = minright;
}
Node* s = NULL;/*頂替實際要被刪除的點的子節點*/
if (tmp->plc != NIL) {
s = tmp->plc;
}
else {
s = tmp->prc;
}
s->pp = parent(tmp);
if (tmp->pp == NIL) {
*root = s;
}
else {
if (tmp == parent(tmp)->plc) {
parent(tmp)->plc = s;
}
else {
parent(tmp)->prc = s;
}
}
if (tmp != s) {
node->key = tmp->key;
node->val = tmp->val;
}
/*實際要刪除的點的顏色,如果是紅色,刪除之後不影響,如果是黑色,則需要調整*/
if (BLACK == tmp->color) {
if (s->color == RED) { /*實際被刪除的點的子節點如果是紅色,直接將其變爲黑色,即滿足性質*/
s->color = BLACK;
}
else {
deleteadjust(root, s);
}
}
}
/*刪除指定key的元素*/
RESULT delete(Node** root, int key)
{
Node* node = search(*root, key);
if (node == NIL)
{
return SUCCESS;
}
/*如果實際刪除的點是個黑色的,就需要調整,不然違背紅黑樹性質*/
_delete_(root,node);
return SUCCESS;
}
測試代碼:
/*test.c*/
#include "RB-Tree.h"
int main()
{
Node* root = NULL;
//int elemarry[] = { 16, 12, 18, 10, 14, 17, 20, 9, 11, 13, 15, 8 };
int elemarry[] = { 1, 6, 11, 13, 15, 17, 22, 25,27 };
for (int i = 0; i < sizeof(elemarry) / sizeof(int); i++)
{
if (SUCCESS != insert(&root, elemarry[i], elemarry[i]))
{
perror("insert failure!!!\n");
return FAILURE;
}
}
delete(&root,17);
return 0;
}
以上代碼寫的比較隨意,基本上保證了代碼邏輯上沒有什麼問題,也闡述了紅黑樹的插入和刪除操作,但是存在一些編碼風格和小的BUG的風險,等到真正使用的時候再去仔細的修訂吧。但是,若您看出了比較明顯的BUG,還請指出,以免錯誤誤導了更多人,謝謝!
好了,到此爲止關於二叉搜索樹的故事就告一段落了,一路走了,我們從BST到AVL,再到RB-Tree,操作越來越複雜,但是其性能也是越來越好,關於其他的一些變種,以後就不介紹了,掌握了這些,將來再去學習那些變種樹也會得心應手了。