1. 程式人生 > >紅黑樹之刪除節點

紅黑樹之刪除節點

易到 特定 1-1 enter 來看 紅孩子 簡單 code 排序

紅黑樹之刪除節點

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

相比於添加節點,刪除節點要復雜的多。不過我們慢慢梳理,還是能夠弄明白的。

回顧一下紅黑樹的性質

紅黑樹是每個節點都帶有顏色屬性的二叉查找樹,顏色或紅色或黑色。在二叉查找樹強制一般要求以外,對於任何有效的紅黑樹我們增加了如下的額外要求:

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

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

我們要刪除的節點類型從大的方面來說,只有兩種:

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

2、 只有右子樹(或只有左子樹)的節點

為什麽這樣呢?

我們知道,對於一棵普通的二叉排序樹來說,刪除的節點情況可以分為3種:

1、 葉子節點

2、 只有左子樹或只有右子樹的節點

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

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

所以情況3可以轉換成情況1或2。

同樣,對於紅黑樹來講,我們實際上刪除的節點情況只有兩種。

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

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

1、

技術分享

2、

技術分享

3、

技術分享

4、

技術分享

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

5、

技術分享

6、

技術分享

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

此外對於刪除紅色節點的情況比較簡單,我們可以先來看看。

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

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

技術分享

技術分享

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

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

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

接下來我們要討論刪除最復雜的情況了,也就是刪除的節點為黑色的情況

同樣,我們也將其分成兩部分來考慮:

1、 刪除黑色的葉子節點

技術分享

技術分享

對於這種情況,相對復雜,後面我們再細分

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

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

技術分享

技術分享

這兩種情況的處理方式是一樣的,即用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是包含添加和刪除節點的

如果你覺得對你有用,請點個贊吧~~~光圖都畫了好長時間~~

紅黑樹之刪除節點