首页 > 代码库 > 红黑树插入删除节点过程分析 && C代码实现
红黑树插入删除节点过程分析 && C代码实现
红黑树的插入和删除规则:
红黑树的五个性质
1、 每个节点要么是红的,要么是黑的
2、 根节点时黑色的
3、 每个叶节点(叶节点既指树尾端NIL指针或NULL节点)是黑色的
4、 如果一个节点时红的,那么它的两个儿子都是黑色的
5、 对每个节点,其到叶节点树尾端NIL指针的每一条路径都包含相同数目的黑节点
这里所说的“叶节点”或者“NULL节点”,它不包含数据而只充当树在此结束的知识。
二叉树的左旋和右旋这里不再讲解
红黑树的插入操作:
在对红黑树进行插入操作时,我们一般总是插入红色的节点,因为这样可以在插入过程中尽量避免对树的调整。
如果插入的节点是根节点,性质2会被破坏,如果插入节点的父是红色,则会破坏性质4.因此,总而言之,插入一个红色节点只会破坏性质2或性质4
其实可以简单处理:
把出现违背红黑树性质的节点向上移动,如果能移动到根节点,那么很容易就能通过直接修改根节点来恢复红黑树的性质,直接通过修改根节点来恢复红黑树应满足的性质。
其二,穷举所有的可能性,之后把能归于同一类方法处理的归为同一类,不能直接处理的归化到下面的几种情况。
插入修复的操作:
情况1:插入的是根节点
直接把此节点涂为黑色
情况2:插入的节点的父节点的黑色
此不会违反性质2和性质4,红黑树没有被破坏 什么都不做
情况3:当前节点的父节点时红色且祖父节点的另一个子节点(叔叔节点)是红色
此时父节点的父节点一定存在,否则插入前就已不是红黑树。与此同时,又分为父节点是祖父节点的左子还是右子,对于对称性,只要知道一个方向即可。
在这里,我们只考虑父节点为祖父左子的情况。
同时,还可以分为当前节点是其父节点的左子还是右子,但是处理方式是一样的。
对策:将当前节点的父节点和叔叔节点涂黑,祖父节点涂红,把当前节点指向祖父节点,从新的当前节点重新开始算法
情况4:当前节点的父节点是红色,叔叔节点是黑色的,当前节点是其父节点的右子
对策:当前节点的父节点作为新的当前节点,以新当前节点为支点左旋
情况5:当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的左子
对策:父节点变为黑色,祖父节点变为红色,在祖父节点为支点右旋
红黑树的删除操作:
我们删除的节点的方法与常规二叉搜索树中删除节点的方法是一样的,如果被删除的节点不是有双非空子女,则直接删除这个节点,用它的位移子节点顶替他的位置,如果他的子节点分是空节点,那就用空节点顶替他的位置,如果他的双子全为非空,我们就把它的直接后继节点内容复制到它的位置,之后以同样的方法删除他的后继节点,他的后继节点不可能是双子非空,因此此传递最多只进行一次
在这里说明一下二叉树节点删除的几种情况,待删除的节点按照儿子的个数可以分为三种:
1、 没有儿子,既为叶子节点。直接把父节点的对应儿子指针设为NULL,删除儿子节点就OK
2、 只有一个儿子。那么把父节点的相应儿子指针指向儿子的独生子,删除儿子节点也OK
3、 有两个儿子,这是组麻烦的情况,因为你删除节点之后,还要保证满足搜索二叉树的结构。其实也比较简单,我们可以选择左儿子中的最大元素或者右儿子的最小元素放到待删除节点的位置,就可以保证结构的不变。当然,你要记得调整子树,毕竟又出现了节点删除。习惯上选择左儿子中的最大元素,其实选择右儿子的最小元素也一样,没有任何差别。只是习惯而已。这里咱们也选择左儿子的最大元素,将它放到待删除节点的位置。左儿子的最大元素其实很好找,只要顺着左儿子不断的去搜索右字数就可以了,直到找到一个没有右子树的节点,那就是最大的了
在删除节点后,原红黑树的性质可能被改变,如果删除的是红色节点,那么原红黑树的性质依旧保持,此时不用做修正操作,如果删除的节点是黑色节点,原红黑树的性质可能会被改变,我们要对其做修正操作。那些性质可能被修改呢?如果删除节点不是树唯一节点,那么删除节点的那一个支的到各叶节点的黑色节点数会发生改变,此时性质5被破坏。如果被删除节点的唯一非空子节点是红色,而被删除节点的父节点也是红色,那么性质4被破坏。如果被删除节点是根节点,而它的唯一非空子节点是红色,则删除后新根节点将变成红色,违背性质2.
情况1:当前节点是红色
情况2:当前节点是黑色且是根节点
情况3:当前节点是黑且兄弟节点为红色(此时父节点和兄弟节点的子节点为黑)
情况4:当前节点是黑且兄弟节点为黑且兄弟节点的两个子节点全为黑色
情况5:当前节点是黑兄弟节点是黑,兄弟的左子是红色,右子是黑色
情况6:当前节点是黑兄弟节点是黑色,但是兄弟节点的右子是红色,兄弟节点左子的颜色任意
其实对于红黑树的插入操作来说,需要明白为什么在插入节点时需要关注叔叔节点。对于插入操作来说,插入的是红色节点,如果父亲节点是黑色节点,那么不需要过多操作,需要关注的父亲节点是红色的情况,那么就需要看叔叔节点什么颜色了,如果叔叔是红色,那么祖父肯定是黑色,如果叔叔是黑色,祖父存在也是红色。
对于删除操作,就需要关注兄弟节点,对于兄弟节点需要关注的就是兄弟的儿子节点了。
代码实现:
//一、左旋代码分析 /*----------------------------------------------------------- | node right | / \ ==> / \ | a right node y | / \ / \ | b y a b //左旋 -----------------------------------------------------------*/ static rb_node_t* rb_rotate_left(rb_node_t* node, rb_node_t* root) { rb_node_t* right = node->right; //指定指针指向 right<--node->right if ((node->right = right->left)) { right->left->parent = node; //好比上面的注释图,node成为b的父母 } right->left = node; //node成为right的左孩子 if ((right->parent = node->parent)) { if (node == node->parent->right) { node->parent->right = right; } else { node->parent->left = right; } } else { root = right; } node->parent = right; //right成为node的父母 return root; } //二、右旋 /*----------------------------------------------------------- | node left | / \ / \ | left y ==> a node | / \ / \ | a b b y //右旋与左旋差不多,分析略过 -----------------------------------------------------------*/ static rb_node_t* rb_rotate_right(rb_node_t* node, rb_node_t* root) { rb_node_t* left = node->left; if ((node->left = left->right)) { left->right->parent = node; } left->right = node; if ((left->parent = node->parent)) { if (node == node->parent->right) { node->parent->right = left; } else { node->parent->left = left; } } else { root = left; } node->parent = left; return root; } //三、红黑树查找结点 //---------------------------------------------------- //rb_search_auxiliary:查找 //rb_node_t* rb_search:返回找到的结点 //---------------------------------------------------- static rb_node_t* rb_search_auxiliary(key_t key, rb_node_t* root, rb_node_t** save) { rb_node_t *node = root, *parent = NULL; int ret; while (node) { parent = node; ret = node->key - key; if (0 < ret) { node = node->left; } else if (0 > ret) { node = node->right; } else { return node; } } if (save) { *save = parent; } return NULL; } //返回上述rb_search_auxiliary查找结果 rb_node_t* rb_search(key_t key, rb_node_t* root) { return rb_search_auxiliary(key, root, NULL); } //四、红黑树的插入 //--------------------------------------------------------- //红黑树的插入结点 rb_node_t* rb_insert(key_t key, data_t data, rb_node_t* root) { rb_node_t *parent = NULL, *node; parent = NULL; if ((node = rb_search_auxiliary(key, root, &parent))) //调用rb_search_auxiliary找到插入结 点的地方 { return root; } node = rb_new_node(key, data); //分配结点 node->parent = parent; node->left = node->right = NULL; node->color = RED; if (parent) { if (parent->key > key) { parent->left = node; } else { parent->right = node; } } else { root = node; } return rb_insert_rebalance(node, root); //插入结点后,调用rb_insert_rebalance修复红黑树 的性质 } //五、红黑树的3种插入情况 //接下来,咱们重点分析针对红黑树插入的3种情况,而进行的修复工作。 //-------------------------------------------------------------- //红黑树修复插入的3种情况 //为了在下面的注释中表示方便,也为了让下述代码与我的倆篇文章相对应, //用z表示当前结点,p[z]表示父母、p[p[z]]表示祖父、y表示叔叔。 //-------------------------------------------------------------- static rb_node_t* rb_insert_rebalance(rb_node_t *node, rb_node_t *root) { rb_node_t *parent, *gparent, *uncle, *tmp; //父母p[z]、祖父p[p[z]]、叔叔y、临时结点*tmp while ((parent = node->parent) && parent->color == RED) { //parent 为node的父母,且当父母的颜色为红时 gparent = parent->parent; //gparent为祖父 if (parent == gparent->left) //当祖父的左孩子即为父母时。 //其实上述几行语句,无非就是理顺孩子、父母、祖父的关系。:D。 { uncle = gparent->right; //定义叔叔的概念,叔叔y就是父母的右孩子。 if (uncle && uncle->color == RED) //情况1:z的叔叔y是红色的 { uncle->color = BLACK; //将叔叔结点y着为黑色 parent->color = BLACK; //z的父母p[z]也着为黑色。解决z,p[z]都是红色的问题。 gparent->color = RED; node = gparent; //将祖父当做新增结点z,指针z上移俩层,且着为红色。 //上述情况1中,只考虑了z作为父母的右孩子的情况。 } else //情况2:z的叔叔y是黑色的, { if (parent->right == node) //且z为右孩子 { root = rb_rotate_left(parent, root); //左旋[结点z,与父母结点] tmp = parent; parent = node; node = tmp; //parent与node 互换角色 } //情况3:z的叔叔y是黑色的,此时z成为了左孩子。 //注意,1:情况3是由上述情况2变化而来的。 //......2:z的叔叔总是黑色的,否则就是情况1了。 parent->color = BLACK; //z的父母p[z]着为黑色 gparent->color = RED; //原祖父结点着为红色 root = rb_rotate_right(gparent, root); //右旋[结点z,与祖父结点] } } else { //这部分是特别为情况1中,z作为左孩子情况,而写的。 uncle = gparent->left; //祖父的左孩子作为叔叔结点。[原理还是与上部分一样的] if (uncle && uncle->color == RED) //情况1:z的叔叔y是红色的 { uncle->color = BLACK; parent->color = BLACK; gparent->color = RED; node = gparent; //同上。 } else //情况2:z的叔叔y是黑色的, { if (parent->left == node) //且z为左孩子 { root = rb_rotate_right(parent, root); //以结点parent、root右旋 tmp = parent; parent = node; node = tmp; //parent与node 互换角色 } //经过情况2的变化,成为了情况3. parent->color = BLACK; gparent->color = RED; root = rb_rotate_left(gparent, root); //以结点gparent和root左旋 } } } root->color = BLACK; //根结点,不论怎样,都得置为黑色。 return root; //返回根结点。 } //六、红黑树的删除 //------------------------------------------------------------ //红黑树的删除结点 rb_node_t* rb_erase(key_t key, rb_node_t *root) { rb_node_t *child, *parent, *old, *left, *node; color_t color; if (!(node = rb_search_auxiliary(key, root, NULL))) //调用rb_search_auxiliary查找要删除的 结点 { printf("key %d is not exist!\n"); return root; } old = node; if (node->left && node->right) { node = node->right; while ((left = node->left) != NULL) { node = left; } child = node->right; parent = node->parent; color = node->color; if (child) { child->parent = parent; } if (parent) { if (parent->left == node) { parent->left = child; } else { parent->right = child; } } else { root = child; } if (node->parent == old) { parent = node; } node->parent = old->parent; node->color = old->color; node->right = old->right; node->left = old->left; if (old->parent) { if (old->parent->left == old) { old->parent->left = node; } else { old->parent->right = node; } } else { root = node; } old->left->parent = node; if (old->right) { old->right->parent = node; } } else { if (!node->left) { child = node->right; } else if (!node->right) { child = node->left; } parent = node->parent; color = node->color; if (child) { child->parent = parent; } if (parent) { if (parent->left == node) { parent->left = child; } else { parent->right = child; } } else { root = child; } } free(old); if (color == BLACK) { root = rb_erase_rebalance(child, parent, root); //调用rb_erase_rebalance来恢复红黑树性 质 } return root; } //七、红黑树的4种删除情况 //---------------------------------------------------------------- //红黑树修复删除的4种情况 //为了表示下述注释的方便,也为了让下述代码与我的倆篇文章相对应, //x表示要删除的结点,*other、w表示兄弟结点, //---------------------------------------------------------------- static rb_node_t* rb_erase_rebalance(rb_node_t *node, rb_node_t *parent, rb_node_t *root) { rb_node_t *other, *o_left, *o_right; //x的兄弟*other,兄弟左孩子*o_left,*o_right while ((!node || node->color == BLACK) && node != root) { if (parent->left == node) { other = parent->right; if (other->color == RED) //情况1:x的兄弟w是红色的 { other->color = BLACK; parent->color = RED; //上俩行,改变颜色,w->黑、p[x]->红。 root = rb_rotate_left(parent, root); //再对p[x]做一次左旋 other = parent->right; //x的新兄弟new w 是旋转之前w的某个孩子。其实就是左旋后 的效果。 } if ((!other->left || other->left->color == BLACK) && (!other->right || other->right->color == BLACK)) //情况2:x的兄弟w是黑色,且w的俩个孩子也 都是黑色的 { //由于w和w的俩个孩子都是黑色的,则在x和w上得去掉一黑色, other->color = RED; //于是,兄弟w变为红色。 node = parent; //p[x]为新结点x parent = node->parent; //x<-p[x] } else //情况3:x的兄弟w是黑色的, { //且,w的左孩子是红色,右孩子为黑色。 if (!other->right || other->right->color == BLACK) { if ((o_left = other->left)) //w和其左孩子left[w],颜色交换。 { o_left->color = BLACK; //w的左孩子变为由黑->红色 } other->color = RED; //w由黑->红 root = rb_rotate_right(other, root); //再对w进行右旋,从而红黑性质恢复。 other = parent->right; //变化后的,父结点的右孩子,作为新的兄弟结点 w。 } //情况4:x的兄弟w是黑色的 other->color = parent->color; //把兄弟节点染成当前节点父节点的颜色。 parent->color = BLACK; //把当前节点父节点染成黑色 if (other->right) //且w的右孩子是红 { other->right->color = BLACK; //兄弟节点w右孩子染成黑色 } root = rb_rotate_left(parent, root); //并再做一次左旋 node = root; //并把x置为根。 break; } } //下述情况与上述情况,原理一致。分析略。 else { other = parent->left; if (other->color == RED) { other->color = BLACK; parent->color = RED; root = rb_rotate_right(parent, root); other = parent->left; } if ((!other->left || other->left->color == BLACK) && (!other->right || other->right->color == BLACK)) { other->color = RED; node = parent; parent = node->parent; } else { if (!other->left || other->left->color == BLACK) { if ((o_right = other->right)) { o_right->color = BLACK; } other->color = RED; root = rb_rotate_left(other, root); other = parent->left; } other->color = parent->color; parent->color = BLACK; if (other->left) { other->left->color = BLACK; } root = rb_rotate_right(parent, root); node = root; break; } } } if (node) { node->color = BLACK; //最后将node[上述步骤置为了根结点],改为黑色。 } return root; //返回root } //八、测试用例 //主函数 int main() { int i, count = 100; key_t key; rb_node_t* root = NULL, *node = NULL; srand(time(NULL)); for (i = 1; i < count; ++i) { key = rand() % count; if ((root = rb_insert(key, i, root))) { printf("[i = %d] insert key %d success!\n", i, key); } else { printf("[i = %d] insert key %d error!\n", i, key); exit(-1); } if ((node = rb_search(key, root))) { printf("[i = %d] search key %d success!\n", i, key); } else { printf("[i = %d] search key %d error!\n", i, key); exit(-1); } if (!(i % 10)) { if ((root = rb_erase(key, root))) { printf("[i = %d] erase key %d success\n", i, key); } else { printf("[i = %d] erase key %d error\n", i, key); } } } return 0; }
参考:http://blog.csdn.net/v_july_v/article/details/6114226 代码来自结构之法算法之道
红黑树插入删除节点过程分析 && C代码实现