紅黑樹之刪除節點

紅黑樹之刪除節點

上一篇文章中講了如何向紅黑樹中添加節點,也順便建立了一棵紅黑樹。今天寫寫怎樣從紅黑樹中刪除節點。node

相比於添加節點,刪除節點要複雜的多。不過咱們慢慢梳理,仍是可以弄明白的。算法

回顧一下紅黑樹的性質

紅黑樹是每一個節點都帶有顏色屬性的二叉查找樹,顏色或紅色或黑色。在二叉查找樹強制通常要求之外,對於任何有效的紅黑樹咱們增長了以下的額外要求:數據結構

  1. 節點是紅色或黑色。
  2. 根節點是黑色。
  3. 每一個葉節點(這裏的葉節點是指NULL節點,在《算法導論》中這個節點叫哨兵節點,除了顏色屬性外,其餘屬性值都爲任意。爲了和之前的葉子節點作區分,原來的葉子節點還叫葉子節點,這個節點就叫他NULL節點吧)是黑色的。
  4. 每一個紅色節點的兩個子節點都是黑色。(從每一個葉子到根的全部路徑上不能有兩個連續的紅色節點,或者理解爲紅節點不能有紅孩子)
  5. 從任一節點到其每一個葉子的全部路徑都包含相同數目的黑色節點(黑節點的數目稱爲黑高black-height)。

 

首先先說一下咱們要刪除節點的類型

咱們要刪除的節點類型從大的方面來講,只有兩種:函數

一、         單個的葉子節點(不是指NULL節點,就是二叉排序樹中的葉子節點的概念)spa

二、         只有右子樹(或只有左子樹)的節點3d

爲何這樣呢?code

咱們知道,對於一棵普通的二叉排序樹來講,刪除的節點狀況能夠分爲3種:blog

一、         葉子節點排序

二、         只有左子樹或只有右子樹的節點文檔

三、         既有左子樹又有右子樹的節點。

咱們知道對於一棵普通二叉樹的狀況3來講,要刪除既有左子樹又有右子樹的節點,咱們首先要找到該節點的直接後繼節點,而後用後繼節點替換該節點,最後按1或2中的方法刪除後繼節點便可。

因此狀況3能夠轉換成狀況1或2。

一樣,對於紅黑樹來說,咱們實際上刪除的節點狀況只有兩種。

 

對於狀況2,也就是待刪除的節點只有左子樹或這有右子樹的狀況,有不少組合在紅黑樹中是不可能出現的,由於他們違背了紅黑樹的性質。

狀況2中不存在的狀況有(其中D表示要刪除的節點,DL和DR分別表示左子和右子):

一、          

 

 

二、

 

 

三、

 

 

四、

 

 

上面這四種明顯都違背了性質5

五、

 

 

六、

 

 

5和6兩種狀況明顯都違背了性質4

 

此外對於刪除紅色節點的狀況比較簡單,咱們能夠先來看看。

咱們上面把待刪除的節點分紅爲了兩種,那這裏,對於刪除的紅色節點,咱們也分兩種:

一、         刪除紅色的葉子節點(D表示待刪除的節點,P表示其父親節點)

 

 

 

上面這兩種狀況其實處理方式都同樣,直接刪除D就好

二、         刪除的紅色節點只有左子樹或只有右子樹

上面已經分析了,紅黑樹中根本不存在這種狀況!!

 

接下來咱們要討論刪除最複雜的狀況了,也就是刪除的節點爲黑色的狀況

一樣,咱們也將其分紅兩部分來考慮:

一、         刪除黑色的葉子節點

 

 

 

對於這種狀況,相對複雜,後面咱們再細分

二、         刪除的黑色節點僅有左子樹或者僅有右子樹

去掉前面已經分析的不存在的狀況。這種狀況下節點的結構只肯能是
(豎直的西線代替了左右分支的狀況)

 

 

 

這兩種狀況的處理方式是同樣的,即用D的孩子(左或右)替換D,並將D孩子的顏色改爲黑色便可(由於路徑上少了一個黑節點,所已將紅節點變成黑節點以保持紅黑樹的性質)

 

因此,這些狀況處理起來都很簡單。。。除了,刪除黑色葉子節點的狀況。

下面重點討論刪除黑色葉子節點的狀況

狀況1:待刪除節點D的兄弟節點S爲紅色

D是左節點的狀況

 

 

調整作法是將父親節點和兄弟節點的顏色互換,也就是p變成紅色,S變成黑色,而後將P樹進行AVL樹種的RR型操做,結果以下圖

 

這個時候咱們會發現,D的兄弟節點變成了黑色,這樣就成後面要討論的狀況。

D是右節點的狀況

 

 

將P和S的顏色互換,也就是將P變成紅色,將S變成黑色,而後對P進行相似AVL樹的LL操做。結果以下圖:

 

 

 此時D的兄弟節點變成了黑色,這樣就成了咱們後面要討論的狀況

 

 

狀況2:兄弟節點爲黑色,且遠侄子節點爲紅色。

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便可。結果以下圖:

 

 

 

狀況3:兄弟節點S爲黑色,遠侄子節點爲黑色,近侄子節點爲紅色

D爲左孩子的狀況,此時近侄子節點爲S的左孩子

 

 

作法是,將SL右旋,並將S和SL的顏色互換,這個時候就變成了狀況4

 

 

 

D爲右孩子的狀況,此時近侄子節點爲S的右孩子

 

 

作法是將S和SR顏色對調,而後對SR進行左旋操做,這樣就變成了狀況4,結果以下圖:

 

 

狀況4:父親節p爲紅色,兄弟節點和兄弟節點的兩個孩子(只能是NULL節點)都爲黑色的狀況。

 

 

若是刪除D,那通過P到D的子節點NULL的路徑上黑色就少了一個,這個時候咱們能夠把P變成黑色,這樣刪除D後通過D子節點(NULL節點)路徑上的黑色節點就和原來同樣了。可是這樣會致使通過S的子節點(NULL節點)的路徑上的黑色節點數增長一個,因此這個時候能夠再將S節點變成紅色,這樣路徑上的黑色節點數就和原來同樣啦!

因此作法是,將父親節點P改爲黑色,將兄弟節點S改爲紅色,而後刪除D便可。以下圖

 

 

狀況5:父親節點p,兄弟節點s和兄弟節點的兩個孩子(只能爲NULL節點)都爲黑色的狀況

 

 

方法是將兄弟節點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 ++;

                  }

 

         }

}

 

最後附上word文檔和源代碼文件

 連接:http://pan.baidu.com/s/1nvQI2iX 密碼:16nd

那個brt2.c是包含添加和刪除節點的

若是你以爲對你有用,請點個贊吧~~~光圖都畫了好長時間~~

相關文章
相關標籤/搜索