二叉平衡樹之刪除節點操作
更好的判斷最小非平衡樹類型的方法
在前一篇文章中,我們知道最小非平衡樹可以分為四種類型,即:LL型、LR型、RR型和RL型。而且我也按照自己的理解,歸納了判斷是哪種類型的方法。總結一下就是:設最小非平衡樹的樹根為unbalance,首先看unbalance的左右子樹誰更高,如果左子樹更高則為LX型、如果是右子樹高則為RX型。再進一步,如果為LX型,將剛剛插入的節點的值value與unbalance左孩子進行比較,如果value大則為LR型,如果value小則為LL型。如果為RX型,將value與unbalance右孩子進行比較,如果value大則為RR型,如果value小則為RL型。
但是這種判斷類型的方法實際上存在不足之處,就是它是適用於插入節點導致二叉樹失衡的情形,因為第二步中要用到剛剛插入的節點的值。如果是刪除節點導致的失衡就不能用這種方法判斷了,因為根本沒有value。
我在思考刪除節點導致失衡的情形時,發現我上面總結的判斷類型的方法實際上沒有總結到根源上。
其實我們調整樹的結構根本目的是為了降低左右子樹的高度差,也就是說調整的目的在於調整左右子樹的高度(當然在調整高度的同時要保證依然是一顆二叉查找樹)。實際上最小非平衡樹的四種類型,實際上就是對左右子樹高度的一個描述、一個分類:
比如LL型:
第一個L表示:unbalance的左子樹比右子樹高。第二個L表示unbalance的左孩子的左子樹比右子樹高。
LR型:
L表示:unbalance的左子樹比右子樹高。R表示unbalance的左孩子的右子樹比左子樹高。
RL型:
R表示:unbalance的右子樹比左子樹高。L表示unbalance的右孩子的左子樹比右子樹高。
RR型:
第一個R表示:unbalance的右子樹比左子樹高。第二個R表示unbalance的右孩子的右子樹比左子樹高。
所以總結一下就是,第一個字母(L或R)描述的是unbalance(也就是根節點)的左右子樹哪個更高,而第二個字母(L或R)描述的是unbalance的孩子(如果第一個是L則為左孩子,反之為右孩子)的左右子樹哪個更高。
這樣,添加節點和刪除節點導致的不平衡問題,對最小非平衡子樹的類型判斷就可以統一起來了!!!!!
另外需要指出,對於添加節點過程,並不存在,最小非平衡子樹的左右子樹等高的情況。比如下圖:

最小非平衡子樹的樹根為12,但是8節點的左右子樹高度相等,這才插入節點操作中是不可能產生的,因為剛剛插入的節點只可能是7或10,但是無論哪個是剛剛插入的節點,在它插入之前就已經不平衡了。比如10是剛剛插入的節點,可以看到,在插入10之前,12就已經不平衡了。
但是這種情況卻可能在刪除節點的過程中出現,比如下圖:

樹本來是平衡的,但是刪除了14后,12就不平衡了,而這個時候8節點的左右子樹等高。這種情形也是LL型。所以對上面類型的分類,還需要加入unbalance的孩子的左右子樹等高的情況,添加之后就是:
LL型:
第一個L表示:unbalance的左子樹比右子樹高。第二個L表示unbalance的左孩子的左子樹比右子樹高,或者unbalance的左孩子的左右子樹等高。
LR型:
L表示:unbalance的左子樹比右子樹高。R表示unbalance的左孩子的右子樹比左子樹高。
RL型:
R表示:unbalance的右子樹比左子樹高。L表示unbalance的右孩子的左子樹比右子樹高。
RR型:
第一個R表示:unbalance的右子樹比左子樹高。第二個R表示unbalance的右孩子的右子樹比左子樹高,或者unbalance的右孩子的左右子樹等高。
理解了上面,下面開始講刪除節點操作
節點刪除操作
對於二叉平衡樹的刪除節點操作,可以分成兩步:
1、 刪除節點,並且保證依然是一棵二叉排序樹
2、 對二叉平衡樹進行調整,保證是一棵平衡二叉樹
對於第二步,在將刪除節點和插入節點導致的非平衡問題中,最小非平衡子樹類型的判斷方法進行了統一后,實際上,對刪除節點導致失衡問題的調整和對於添加節點導致的失衡問題的調整,方法也就統一了!
所以重點在第一步,即:刪除節點,並且保證依然是一棵二叉排序樹。
當刪除一個節點時,該節點的孩子們怎么分配十分關鍵,(父親死之前要安排他的孩子,可憐天下父母心呀!)因為要保證依然是一棵二叉排序樹。
根據待刪除節點的孩子的分布特點,可以將待刪除節點分成三種類型,我們設待刪除的節點為todelete:
1、 todelete沒有孩子,這種情況最簡單了,直接把它刪了就好,因為它沒有孩子要分配。
2、 todelete只有一個孩子,這種情況也比較簡單,用這個唯一的孩子將todelete取代了就好,所謂子承父業。
3、 todelete有兩個孩子,這種情況就比較復雜了。俗話說一碗水端不平,究竟將自己的皇位傳給哪個孩子呢?這可是個嚴肅的問題!中國古代講究宗法制,血統很重要,按照宗法制,皇位最合法的繼承者就是嫡長子!那在二叉排序樹中誰是todelete的嫡長子呢?答案是todelete的后繼節點,也就是比todelete大的節點中的最小的那一個!看到了吧,為什么當初起名字的時候要叫它后繼節點呢。人家是要繼承皇位噠!
太子繼承了皇位,那太子的位置就空了,這個時候就需要有人來繼承太子的位置,那個人就是原太子的右孩子。好把,我們形式化一下,設todelete的后繼節點為y,y的右孩子為x(注意y是沒有左孩子的,因為如果存在左孩子那他的左孩子就是后繼節點,繼承皇位就沒他什么事兒啦!)。這個時候,如果x為NULL那么,將todelete用y的替換,然后釋放原來的y空間即可。如果x存在,那么用y替換了todelete后,還要用x替換y,然后釋放原來x的空間,也就是太子成為皇帝,太子的兒子成為太子,然后太子的兒子的位置就空了,空了就清理了!
兩張圖,說明一下:


這里要多說一句,對於單鏈表來說,如果用一個節點替換另一個節點,比如:用C替換B,我們可以先斷開AB之間的鏈,然后后將A鏈接到C,然后斷開BC之間的鏈,最后把B回收。當然我們也可以不斷開鏈,而是用C的內容替換B的內容,然后回收C。對於樹來說,也有這兩種方法,但是采用值替換的方法更簡單一下,原因嘛,是因為,單鏈表就一條鏈,而樹是會分叉的,會有多條鏈的!
當然還有一點需要注意,就是當后繼節點y是todelete的兒子的時候,和y是todelete的孫子(或者更后的位置)時,且x為NULL的時候,編寫代碼時會有一點出入。看代碼的時候就明白了(可能你用其他方法,可以避免這種差異,也說不定)
刪除部分的代碼:
/** * 刪除指定的節點 * */ void delete_node(PBNode root,int value) { //找到該節點 PBNode node = get_node(root,value); if(node == NULL) return; //找到副節點 PBNode p_node = get_father(root,value); if(node->l_tree == NULL) { PBNode r = node->r_tree; if(r == NULL)//為葉子節點的情況 { //直接刪除該節點即可 if(p_node != root)//反之,說明整個二叉樹就一個根節點 { if(p_node->l_tree == node) { p_node->l_tree = NULL; } if(p_node->r_tree == node) { p_node->r_tree = NULL; } } free_node(&node); } else//只有右子樹的情況,采用值替換的方法比較科學 { node->l_tree = r->l_tree; node->r_tree = r->r_tree; node->value = r->value; free_node(&r); } } else if(node->r_tree == NULL)//只有左子樹的情況,采用值替換的方法比較科學 { PBNode l = node->l_tree; node->l_tree = l->l_tree; node->r_tree = l->r_tree; node->value = l->value; free_node(&l); } else//既有左孩子又有右孩子,這種情況下我采用的是值交換的方式,這樣比較簡單,如果采用斷鏈再重新指向的方法代碼量就會加大 { //找到后繼節點 PBNode y = node->r_tree; PBNode y_p = y;//用於保存后繼節點的父親節點 while(y->l_tree != NULL) { y_p = y; y = y->l_tree; } PBNode x = y->r_tree; //后繼節點的右孩子 //替換后繼節點和待刪除的節點 node->value = y->value; if(x == NULL)//后繼節點沒有右孩子(根據后繼節點的定義可以知道,后繼節點一定沒有左孩子) { //后繼節點沒有右孩子的情況,在交換了后記節點和待刪除節點的值后,直接刪除后記節點即可 if(y_p == y)//后繼節點就是待刪除節點的右孩子 { node->r_tree = NULL; } else { y_p->l_tree = NULL;//后繼節點是待刪除節點的右子樹的最左節點 } free_node(&y); } else { //替換后繼節點的右孩子和后繼節點 y->l_tree = x->l_tree; y->r_tree = x->r_tree; y->value = x->value; free_node(&x); } } }
對於第二步,使二叉樹平衡,就再調用一下平衡的函數就好,我已經將刪除節點和添加節點后引起的不平衡問題的處理方法進行了統一。
最后附上源文件的鏈接和密碼(那個bst2.c是包含刪除節點操作的,而且將調整的方法進行了統一)
鏈接:http://pan.baidu.com/s/1slyjF6t 密碼:k752
如果對你有用,請贊一個吧~~
