刪除操作比插入複雜一些。首先我們先來了解一些紅黑樹的特性。這些是我隨意列舉的,供大家參考。
1、紅色節點的父親黑孩子一定是黑色。(nil是黑節點)
2、單支節點,只能是黑紅。(紅黑,黑黑,不符合規則4,到樹尾黑節點個數相同)
3、真正的刪除節點一定是單支節點或者葉子節點。(沒有孩子的節點)
接下來我們講如何找真正的刪除節點。
有左右子樹的情況
如果8是刪除節點,那麼10就是真正的刪除節點。
查找方法是,找8節點的右子樹中的最小的節點,根據二叉樹的性質,我們可以知道,8節點的右孩子的左孩子的左孩子……一直到left爲nil的節點。就是10。(while ( y->left != nil ) y = y->left;)
單支節點或者沒有葉子節點的情況
刪除節點跟真正的刪除節點是一個。
找到真正的刪除節點後,我們把刪除節點的值變成真正的刪除節點的值。這時候把真正的刪除節點從紅黑樹中剔除。
如果真正的刪除節點是黑色,則破壞了紅黑樹的性質,進行調整。(比如刪除8,真正的刪除節點是10,10是黑色節點,所以需要調整紅黑樹)
void rb_remove(const int key, Tree * tree) { Node * x, * y, * z;//x是刪除點,y是真正的刪除點,z是y的子節點 x = search_node(key, tree); if ( x == nil || x->value != key ) return; if ( x->left != nil && x->right != nil )//找到真正的刪除節點 { y = x->right; while ( y->left != nil ) y = y->left; } else y = x; if ( y->left != nil ) z = y->left; else z = y->right; if ( y == tree->root ) { tree->root = z; z->parent = nil; } else { if ( y == y->parent->left ) y->parent->left = z; else y->parent->right = z; if ( z != nil ) z->parent = y->parent; } assign(x, y); if ( y->color == black ) remove_fixup(z, y->parent, tree); free(y); }
在將真正的刪除節點剔除時,注意它是否有孩子,如果右孩子,將原來指向真正刪除節點的指針指向孩子,不要忘了將更改孩子的父親。否則將原來指向真正刪除節點的指針指向nil。
接下來刪除調整
刪除調整的實現有很多種,不過答題思路都是一樣的。如果自己寫的話,需要把各種情況考慮清楚。
static void remove_fixup(Node * x, Node * y, Tree * tree) { Node * z;//z是x的兄弟 while ( x != tree->root && x->color == black ) { if ( x == y->left ) { z = y->right; //case 1: 兄弟是紅色 //處理方法: // 1.將兄弟設爲黑色 // 2.將父親設爲紅色 // 3.以父親爲旋轉點,左旋 // 4.重置x的兄弟節點 // 變成case 2, 3, 4 if ( z->color == red ) { z->color = black; y->color = red; left_rotate(y, tree); z = y->right; } //case 2: 兄弟是黑色,並且兩個孩子是黑色 //處理方法: // 1.將兄弟設爲紅色 // 2.將x設爲父親 if ( z->left->color == black && z->right->color == black ) { z->color = red; x = y; y = x->parent; } //case 3: 兄弟是黑色,左孩子是紅色,右孩子是黑色 //處理方法; // 1.將兄弟的左孩子設爲黑色 // 2.將兄弟設爲紅色 // 3.以兄弟爲旋轉點,右旋 // 4.重新設置兄弟節點 else { if ( z->right->color == black ) { z->left->color = black; z->color = red; right_rotate(z, tree); z = y->right; } //case 4: 兄弟是黑色,右孩子是紅色 //處理方法: // 1.將兄弟的顏色設爲父親的顏色 // 2.將父親的顏色設爲黑色 // 3.將兄弟的右孩子設爲黑色 // 4.以父親爲旋轉點,左旋 z->color = y->color; y->color = black; z->right->color = black; left_rotate(y, tree); break; } } else { z = y->left; if ( z->color == red ) { y->color = red; z->color = black; right_rotate(y, tree); z = y->left; } if ( z->left->color == black && z->right->color == black ) { z->color = red; x = y; y = x->parent; } else { if ( z->left->color == black ) { z->right->color = black; z->color = red; left_rotate(z, tree); z = y->left; } z->color = y->color; y->color = black; z->left->color = black; right_rotate(y, tree); break; } } } if ( x != nil ) x->color = black; }
如果x是y的右孩子,操作跟左孩子相同,把left與right交換。
刪除操作完成,建議大家在調試把紅黑樹畫出來。
這裏附上我的調試代碼。
#include <stdio.h> #include <stdlib.h> #include <time.h> #include "rbtree.h" void print(Node * x) { printf("%d\t", x->value); if ( x->color == red ) printf("red\t"); else printf("black\t"); if ( x->parent ) printf("parent value = %d\n", x->parent->value); else printf("\n"); } int main(void) { Tree tree; int i; srand(time(NULL)); rb_init(&tree); for ( i = 0; i < 100; i++ ) { rb_insert(rand()%1000, &tree); } rb_treaverse(&tree, print); for ( i = 0; i < 100; i++ ) { rb_remove(rand()%1000, &tree); } // rb_insert(10, &tree); // rb_insert(7, &tree); // rb_insert(8, &tree); // rb_insert(15, &tree); // rb_insert(5, &tree); // rb_insert(6, &tree); // rb_insert(11, &tree); // rb_insert(13, &tree); // rb_insert(12, &tree); // rb_insert(2, &tree); // // rb_treaverse(&tree, print); // rb_remove(5, &tree); // rb_remove(7, &tree); // rb_remove(6, &tree); // rb_remove(8, &tree); // rb_treaverse(&tree, print); // rb_remove(2, &tree); // rb_remove(10, &tree); // rb_remove(11, &tree); // rb_remove(12, &tree); // rb_remove(15, &tree); // rb_remove(13, &tree); rb_treaverse(&tree, print); return 0; }
前邊的兩個for循環主要測試紅黑樹是否有bug,如果發現bug,用下面的插入查找bug。
下邊的代碼四種情況基本都會用到。