删除操作比插入复杂一些。首先我们先来了解一些红黑树的特性。这些是我随意列举的,供大家参考。
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 ", x->value); if ( x->color == red ) printf("red "); else printf("black "); if ( x->parent ) printf("parent value = %d ", x->parent->value); else printf(" "); } 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。
下边的代码四种情况基本都会用到。