上一篇文章中講了如何向紅黑樹中添加節點,也順便建立了一棵紅黑樹。今天寫寫怎樣從紅黑樹中刪除節點。node
相比於添加節點,刪除節點要複雜的多。不過咱們慢慢梳理,仍是可以弄明白的。算法
紅黑樹是每一個節點都帶有顏色屬性的二叉查找樹,顏色或紅色或黑色。在二叉查找樹強制通常要求之外,對於任何有效的紅黑樹咱們增長了以下的額外要求:數據結構
咱們要刪除的節點類型從大的方面來講,只有兩種:函數
一、 單個的葉子節點(不是指NULL節點,就是二叉排序樹中的葉子節點的概念)spa
二、 只有右子樹(或只有左子樹)的節點3d
爲何這樣呢?code
咱們知道,對於一棵普通的二叉排序樹來講,刪除的節點狀況能夠分爲3種:blog
一、 葉子節點排序
二、 只有左子樹或只有右子樹的節點文檔
三、 既有左子樹又有右子樹的節點。
咱們知道對於一棵普通二叉樹的狀況3來講,要刪除既有左子樹又有右子樹的節點,咱們首先要找到該節點的直接後繼節點,而後用後繼節點替換該節點,最後按1或2中的方法刪除後繼節點便可。
因此狀況3能夠轉換成狀況1或2。
一樣,對於紅黑樹來說,咱們實際上刪除的節點狀況只有兩種。
對於狀況2,也就是待刪除的節點只有左子樹或這有右子樹的狀況,有不少組合在紅黑樹中是不可能出現的,由於他們違背了紅黑樹的性質。
一、
二、
三、
四、
上面這四種明顯都違背了性質5
五、
六、
5和6兩種狀況明顯都違背了性質4
咱們上面把待刪除的節點分紅爲了兩種,那這裏,對於刪除的紅色節點,咱們也分兩種:
一、 刪除紅色的葉子節點(D表示待刪除的節點,P表示其父親節點)
上面這兩種狀況其實處理方式都同樣,直接刪除D就好!
二、 刪除的紅色節點只有左子樹或只有右子樹
上面已經分析了,紅黑樹中根本不存在這種狀況!!
一樣,咱們也將其分紅兩部分來考慮:
一、 刪除黑色的葉子節點
對於這種狀況,相對複雜,後面咱們再細分
二、 刪除的黑色節點僅有左子樹或者僅有右子樹
去掉前面已經分析的不存在的狀況。這種狀況下節點的結構只肯能是
(豎直的西線代替了左右分支的狀況)
這兩種狀況的處理方式是同樣的,即用D的孩子(左或右)替換D,並將D孩子的顏色改爲黑色便可(由於路徑上少了一個黑節點,所已將紅節點變成黑節點以保持紅黑樹的性質)。
因此,這些狀況處理起來都很簡單。。。除了,刪除黑色葉子節點的狀況。
D是左節點的狀況
調整作法是將父親節點和兄弟節點的顏色互換,也就是p變成紅色,S變成黑色,而後將P樹進行AVL樹種的RR型操做,結果以下圖
這個時候咱們會發現,D的兄弟節點變成了黑色,這樣就成後面要討論的狀況。
D是右節點的狀況
將P和S的顏色互換,也就是將P變成紅色,將S變成黑色,而後對P進行相似AVL樹的LL操做。結果以下圖:
此時D的兄弟節點變成了黑色,這樣就成了咱們後面要討論的狀況
D爲左孩子對的狀況,這時D的遠侄子節點爲S的右孩子
沒有上色的節點表示黑色紅色都可,注意若是SL爲黑色,則SL必爲NULL節點。
這個時候,若是咱們刪除D,這樣通過D的子節點(NULL節點)的路徑的黑色節點個數就會減1,可是咱們看到S的孩子中有紅色的節點,若是咱們能把這棵紅色的節點移動到左側,並把它改爲黑色,那麼就知足要求了,這也是爲何P的顏色無關,由於調整過程只在P整棵子樹的內部進行。
調整過程爲,將P和S的顏色對調,而後對P樹進行相似AVL樹RR型的操做,最後把SR節點變成黑色,並刪除D便可。
D爲右孩子的狀況,此時D的遠侄子爲S的左孩子
一樣,將P和S的顏色對調,而後再對P樹進行相似AVL樹RL型的操做,最後將SR變成黑色,並刪掉D便可。結果以下圖:
D爲左孩子的狀況,此時近侄子節點爲S的左孩子
作法是,將SL右旋,並將S和SL的顏色互換,這個時候就變成了狀況4。
D爲右孩子的狀況,此時近侄子節點爲S的右孩子
作法是將S和SR顏色對調,而後對SR進行左旋操做,這樣就變成了狀況4,結果以下圖:
若是刪除D,那通過P到D的子節點NULL的路徑上黑色就少了一個,這個時候咱們能夠把P變成黑色,這樣刪除D後通過D子節點(NULL節點)路徑上的黑色節點就和原來同樣了。可是這樣會致使通過S的子節點(NULL節點)的路徑上的黑色節點數增長一個,因此這個時候能夠再將S節點變成紅色,這樣路徑上的黑色節點數就和原來同樣啦!
因此作法是,將父親節點P改爲黑色,將兄弟節點S改爲紅色,而後刪除D便可。以下圖:
方法是將兄弟節點S的顏色改爲紅色,這樣刪除D後P的左右兩支的黑節點數就相等了,可是通過P的路徑上的黑色節點數會少1,這個時候,咱們再以P爲起始點,繼續根據狀況進行平衡操做(這句話的意思就是把P當成D(只是不要再刪除P了),再看是那種狀況,再進行對應的調整,這樣一直向上,直到新的起始點爲根節點)。結果以下圖:
我這裏總結的是如何判斷是那種類型,至於特定類型的處理方法,就找前面的內容就好。
記住一句話:判斷類型的時候,先看待刪除的節點的顏色,再看兄弟節點的顏色,再看侄子節點的顏色(侄子節點先看遠侄子再看近侄子),最後看父親節點的顏色。把握好這一點,寫代碼思路就清晰了。
流程圖以下(忽略了處理過程)
節點的數據結構
//定義節點的顏色 enum color{ BLACK, RED }; //節點的數據結構 typedef struct b_node{ int value;//節點的值 enum color color;//樹的深度 struct b_node *l_tree;//左子樹 struct b_node *r_tree;//右子樹 struct b_node *parent;//父親節點 } BNode,*PBNode; /** * 分配一個節點 * */ PBNode allocate_node() { PBNode node = NULL; node = (PBNode)malloc(sizeof(struct b_node)); if(node == NULL) return NULL; memset(node,0,sizeof(struct b_node)); return node; } /** * 設置一個節點的值 * */ void set_value(PBNode node,int value) { if(node == NULL) return; node->value = value; node->color = RED; node->l_tree = NULL; node->r_tree = NULL; node->parent = NULL; } 釋放節點空間的函數 /** * 釋放節點空間 * */ void free_node(PBNode *node) { if(*node == NULL) return; free(*node); *node = NULL; }
後面是與刪除有關的函數,咱們由易到難,先小後大進行處理。
首先,咱們先寫一個刪除節點的函數:
/** * 刪除一個節點 * 其中root爲整棵樹的根結點 * d爲待刪除的節點,或者新的起始點 * */ void delete_node(PBNode *root,PBNode d) { PBNode p = d->parent;//父親節點 if(p == NULL)//說明d就是樹根 { free_node(root); return; } if(p->l_tree == d) p->l_tree = NULL; else if(p->r_tree == d) p->r_tree = NULL; free_node(&d); }
刪除紅色節點的狀況很是簡單,只須要刪除節點就行,因此直接調用刪除函數便可。
/** * 刪除紅色節點 * */ void delete_d_red(PBNode *root,PBNode d) { delete_node(root,d); } 刪除黑色節點的狀況比較複雜,咱們先處理小的模塊: 黑色節點非葉子節點 /** * 黑色節點不是葉子節點,這時候它只有一個孩子,且孩子的顏色爲紅色 * * */ void delete_d_black_not_leaf(PBNode *root,PBNode d) { PBNode dl_r; if(d->l_tree != NULL) { dl_r = d->l_tree; } else if(d->r_tree != NULL) { dl_r = d->r_tree; } else { printf("節點有問題!\n"); return; } dl_r->color = BLACK; PBNode p = d->parent;//父親節點 if(p == NULL)//說明是整棵樹的樹根 { *root = dl_r; } else { if(p->l_tree == d) { p->l_tree = dl_r; } else if(p->r_tree == d) { p->r_tree = dl_r; } } //別忘了修改父親節點 dl_r->parent = p; free_node(&d); }
刪除黑色葉子節點是最複雜的一種狀況,這種狀況整體要再循環中進行,循環結束條件爲:新的起始節點爲根節點。固然,若是循環中某種類型變換完成後,能夠肯定整棵樹都知足紅黑樹,循環也就結束了。
D爲葉子節點且兄弟節點爲紅色的狀況(也就是狀況1):
這種狀況涉及RR型變換和RL型變換,因此咱們先寫一個函數用來處理RR型和RL型變換。
/** * RR類型和LL類型的變換 * */ void avl_trans(PBNode *root,PBNode ch_root,enum unbalance_type type) { int t = type; PBNode small; PBNode middle; PBNode big; switch (t) { case TYPE_LL: { //肯定small、middle、big三個節點 big = ch_root; middle = ch_root->l_tree; small = ch_root->l_tree->l_tree; //分配middle節點的孩子,給small和big big->l_tree = middle->r_tree; //別忘了該父親節點!!!!!!!!! if(middle->r_tree != NULL) middle->r_tree->parent = big; //將small和big做爲midlle的左子和右子 middle->r_tree = big; break; } case TYPE_RR: { //肯定small、middle、big三個節點 small =ch_root; middle = ch_root->r_tree; big = ch_root->r_tree->r_tree; //分配middle節點的孩子,給small和big small->r_tree = middle->l_tree; //別忘了該父親節點!!!!!!!!! if(middle->l_tree != NULL) middle->l_tree->parent = small; //將small和big做爲midlle的左子和右子 middle->l_tree = small; break; } } //將子樹的父親節點的子節點指向middle(也就是將middle,調整後的子樹的根結點) if(ch_root->parent == NULL) //說明子樹的根節點就是整棵樹的根結點 { *root = middle; } else if(ch_root->parent->l_tree == ch_root)//根是父親的左孩子 { ch_root->parent->l_tree = middle; } else if(ch_root->parent->r_tree == ch_root)//根是父親的右孩子 { ch_root->parent->r_tree = middle; } //更改small、middle、big的父親節點 middle->parent = ch_root->parent; big->parent = middle; small->parent = middle; }
有了這兩個變換的函數後,對於兄弟節點爲紅色的這種狀況,處理起來就很簡單了。
/** * D爲黑色,S爲紅色的狀況 * 也就是狀況1 * 將其類型變換成D爲黑色,S也爲黑色的狀況 * */ void delete_black_case1(PBNode *root,PBNode d) { PBNode p = d->parent;//父親節點 if(p->l_tree == d)//d爲左子的狀況 { PBNode s = p->r_tree; p->color = RED;//父親節點變成紅色 s->color = BLACK;//兄弟節點變成黑色 avl_trans(root,p,TYPE_RR); } else if(p->r_tree == d)//d爲右子的狀況 { PBNode s = p->l_tree; p->color = RED;//父親節點變成紅色 s->color = BLACK;//兄弟節點變成黑色 avl_trans(root,p,TYPE_LL); } }
S爲黑色,遠侄子節點爲紅色的狀況(也就是狀況2):
/** * D爲黑色,S爲黑色,遠侄子節點爲紅色 * 也就是狀況2 * */ void delete_black_case2(PBNode *root,PBNode d) { PBNode p = d->parent;//父親節點 if(p->l_tree == d)//d爲左孩子的狀況 { PBNode s = p->r_tree;//兄弟節點 //交換父親姐弟和兄弟節點的顏色 enum color temp = p->color; p->color = s->color; s->color = temp; PBNode far_nephew = s->r_tree;//遠侄子節點 far_nephew->color = BLACK;//將遠侄子節點的顏色變成黑色 avl_trans(root,p,TYPE_RR);//進行相似AVL樹RR類型的轉換 } else if(p->r_tree == d)//d爲右孩子的狀況o { PBNode s = p->l_tree;//兄弟節點 //交換父親姐弟和兄弟節點的顏色 enum color temp = p->color; p->color = s->color; s->color = temp; PBNode far_nephew = s->l_tree;//遠侄子節點 far_nephew->color = BLACK;//將遠侄子節點的顏色變成黑色 avl_trans(root,p,TYPE_LL);//進行相似AVL樹LL類型的轉換 } }
D爲黑色,S爲黑色,遠侄子爲黑色,近侄子爲紅色的狀況(也就是狀況3)
這種狀況涉及節點的左旋和右旋操做,因此寫一個函數處理節點的旋轉
/** * 處理左旋和右旋操做 * */ void node_rotate(PBNode to_rotate,enum rotate_type type) { PBNode p = to_rotate->parent;//父親節點 PBNode g = p->parent;//祖父節點 int t = type; switch(t) { case TURN_RIGHT: { g->r_tree = to_rotate; p->l_tree = to_rotate->r_tree; to_rotate->r_tree = p; break; } case TURN_LEFT: { g->l_tree = to_rotate; p->r_tree = to_rotate->l_tree; to_rotate->l_tree = p; break; } } //別忘了更改父親節點 to_rotate->parent = g; p->parent = to_rotate; }
有了旋轉操做,剩下的就只有顏色變換了。
/** * D爲黑色,S爲黑色,遠侄子爲黑色,近侄子爲紅色 * 也就是狀況3 * 經過旋轉近侄子節點,和相關顏色變換,使狀況3變成狀況2 * */ void delete_black_case3(PBNode d) { PBNode p = d->parent;//父親節點 if(p->l_tree == d)//d爲左孩子的狀況 { PBNode s = p->r_tree; PBNode near_nephew = s->l_tree; s->color = RED; near_nephew->color = BLACK; node_rotate(near_nephew,TURN_RIGHT); } else if(p->r_tree == d) { PBNode s = p->l_tree; PBNode near_nephew = s->r_tree; s->color = RED; near_nephew->color = BLACK; node_rotate(near_nephew,TURN_LEFT); } }
父親節p爲紅色,兄弟節點和兄弟節點的兩個孩子(只能是NULL節點)都爲黑色的狀況,也就是狀況4
這種狀況比較簡單,只涉及顏色的改變
/** * D爲黑色,S爲黑色,遠侄子爲黑色,近侄子爲黑色,父親爲紅色 * 也就是狀況4 * */ void delete_black_case4(PBNode d) { PBNode p = d->parent;//父親節點 if(p->l_tree == d)//d爲左孩子 { PBNode s = p->r_tree; s->color = RED; } else if(p->r_tree == d)//d爲左孩子 { PBNode s = p->l_tree; s->color = RED; } p->color = BLACK; }
父親節點p,兄弟節點s和兄弟節點的兩個孩子(只能爲NULL節點)都爲黑色的狀況,也就是狀況5
這種狀況也比較簡單,就是將S的顏色變成紅色,將起始點有d變成p便可
/** * D,S,P,SL,SR都爲黑色的狀況 * 也就是狀況5 * */ PBNode delete_black_case5(PBNode d) { PBNode p = d->parent;//父親節點 if(p->l_tree == d)//d爲左孩子 { PBNode s = p->r_tree; s->color = RED; } if(p->r_tree == d)//d爲左孩子 { PBNode s = p->l_tree; s->color = RED; } return p; }
最後要寫一個串聯函數,將刪除黑色葉子節點的各個函數串聯起來,這個串聯函數中有循環,循環結束條件是新的起始點爲根節點,可是因爲狀況1-4,處理結束後,整棵樹就是紅黑樹了,此時能夠用break退出循環。
/** * 刪除黑色葉子節點的函數,會將上面的多個函數串連起來 * */ void delete_d_black_leaf(PBNode *root,PBNode d) { PBNode begin = d;//起始節點 while(begin != *root) { PBNode p = begin->parent;//父親節點 if(p->l_tree == begin)//d爲左孩子 { PBNode s = p->r_tree;//兄弟節點 if(s->color == RED)//狀況1 { delete_black_case1(root,begin); continue; } PBNode sl = s->l_tree;//近侄子 PBNode sr = s->r_tree;//遠侄子 if(sr != NULL && sr->color == RED)//狀況2 { delete_black_case2(root,begin); break; } if(sl != NULL && sl->color == RED)//狀況3 { delete_black_case3(begin); continue; } if(p->color == RED)//狀況4 { delete_black_case4(begin); break; } //狀況5 begin = delete_black_case5(begin);//起始點要變換 continue; } else if(p->r_tree == begin)//d爲左孩子 { PBNode s = p->l_tree;//兄弟節點 if(s->color == RED)//狀況1 { delete_black_case1(root,begin); continue; } PBNode sl = s->l_tree;//遠侄子 PBNode sr = s->r_tree;//近侄子 //必定要先看遠侄子,再看近侄子 if(sl != NULL && sl->color == RED)//狀況2 { delete_black_case2(root,begin); break; } if(sr != NULL && sr->color == RED)//狀況3 { delete_black_case3(begin); continue; } if(p->color == RED)//狀況4 { delete_black_case4(begin); break; } //狀況5 begin = delete_black_case5(begin);//起始點要變換 continue; } } //循環退出後,刪除d delete_node(root,d); }
最後寫一個函數將刪除紅色節點、黑色非葉子節點和黑色葉子節點的函數合併到一塊兒,此外還要根據二叉排序樹的要求處理有兩個孩子的節點
/** * 刪除節點函數,並進行調整,保證調整後任是一棵紅黑樹 * */ void delete_brt_node(PBNode *root,int value) { //找到該節點 PBNode node = get_node(*root,value); if(node == NULL) return; int tag = 0; while(tag != 2) { if(node->l_tree == NULL) { PBNode r = node->r_tree; if(r == NULL)//爲葉子節點的狀況 { if(node->color == RED) { delete_d_red(root,node); } else { delete_d_black_leaf(root,node); } break; } else//只有右子樹的狀況 { delete_d_black_not_leaf(root,node); break; } } else if(node->r_tree == NULL)//只有左子樹的狀況 { delete_d_black_not_leaf(root,node); break; } else//既有左孩子又有右孩子 { //找到後繼節點 PBNode y = node->r_tree; while(y->l_tree != NULL) { y = y->l_tree; } //用後繼節點和該節點進行值交換 int temp = node->value; node->value = y->value; y->value = temp; node = y;//待刪除的節點轉換成後繼節點 tag ++; } } }
連接:http://pan.baidu.com/s/1nvQI2iX 密碼:16nd
那個brt2.c是包含添加和刪除節點的
若是你以爲對你有用,請點個贊吧~~~光圖都畫了好長時間~~