二叉查找树与红黑树原理和程序全面介绍
2012-12-08 21:39
267 查看
转载请注明出处/article/1653021.html
0.序
红黑树是块硬骨头。其硬在红黑树必须满足那5条性质。其中4)红结点必须有两个黑孩子结点 5)对每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑结点。这两个性质是在插入与删除中会被破坏的性质。插入破坏性质4,删除破坏性质5.
红黑树的5条性质:
当然红黑树本质上是一个二叉查找树,因此为了研究红黑树,我们首先必须学习二叉查找树。
一.二叉查找树
1.性质
学习一个数据结构,必须先明白它的一些性质。对于二叉查找树,其性质:
结点的左子树总是小于
该结点;结点的右子树总是大于该结点。
2.叶子结点的表示方法:
在算法导论中,叶子结点的表示是用一个结点nil[T]来表示。所有的叶子结点都指向这个结点nil[T],该结点为哨子结点,与普通结点具有相同的域。如下图所示,下图是借用算法导论中红黑树的图来进行说明。
注意:叶子结点的描述对于后续的操作十分重要
3.程序中所使用的数据结构:(这儿使用的红黑树中的数据结构,去掉color成员变量即可)
1)结点结构,包括key,left,right,parent,color
2)树的结构,包含根结点root,叶子节点sentinel,以及插入函数insert(这个是在红黑树中使用)
4.操作之查找
对于二叉查找树的查找而言,十分简单。
查找可以采用递归的做法,但是递归调用函数,会造成很多资源的浪费。采用非递归方式来进行查找。
5.操作之某个结点的孩子中最小的孩子结点
对于二叉查找树而言,其孩子结点中最小的孩子结点,肯定是其最左下角的孩子结点。
6.创建结点
7.遍历二叉树(中序遍历)
8.操作之插入结点
二叉查找树的插入结点A比较简单,只要找到叶子结点,用结点A替换掉这个叶子结点即可。特别注意的是指针间的变化。
即插入结点为node。切记一定要对插入结点node的指针进行修改。
首先,必须考虑的是空树的情况,因为如果是空树,我们必须修改rbtree_t中root指针。
其次我们要找到 插入该结点node的位置。该 位置为叶子结点,我们需要找到这个位置的父结点,记该父结点为temp。
最后,我们需要修改该父结点temp和结点node的指针即可。
二叉树插入的完整代码
9.操作之删除结点
我们记删除的结点为node,实际被删除的结点为subst,要取代该删除结点subst的结点为temp,叶子结点为sentinel
1)删除分为几种情况:
case1:删除结点node的两个孩子均为叶子结点sentinel,那么subst就是node,temp为叶子结点,直接删除即可。
case2:删除结点node只有一个孩子child,那么subst就是node,temp就是该孩子结点child,更改其指针即可。
case3:情况稍微复杂。删除结点node有两个孩子结点lchild 和rchild,我们要寻找rchild这颗子树上最小的结minnode,那么subst就是minnode。 令node的key的取值等于minnode的key的取值,然后删除subst(也就是minnode)即可,情况转化为case1 or case2。
2)我们将上面这三种情况进一步说明:
上述三种情况中,有以下几个共性和个性点。共性1:都是实际删除结点subst的父结点的孩子指针 =取代subst的结点 temp。共性2:都要修改temp的父结点指针(即使temp为叶子结点),temp的父结点指针即为subst的父结点指针。通过共性1和共性2,实现了这一对结点中parent指针和left、right指针的对应,这种对应关系,在二叉树中是十分重要的。(算法导论中关于叶子结点的说明:对于一颗红黑树T来说,哨兵nil[T]是一个与树内普通结点有相同域的对象。它的color域为BLACK,而它的其他域-p,left,right
and key 可以设置成任意允许的值。)
终结版说明:
我们可以将上述三种情况概括为一种通用的方式。我们会发现上述三种情况都是对真正要删除的结点进行处理。
(1)首先寻找真正要删除的结点subst和要取代subst的结点temp。对于case1 and case2 而言,subst就是node,temp就是subst的某个子节点;而对于case3而言,subst是node右子树的最小结点,temp就是subst的右结点(因为如果subst有左节点,那么subst就不是node的右子树的最小结点了)。因此只要找到subst,即可找到结点temp。
(2)修改subst和temp相应的指针。如果删除的根结点,那么还需要修改rbtree_t中的root指针。
( 3)是否需要修改node的key值。当然,我们不能忘记case3中subst为minnode时,需要node的key的取值等于minnode的key的取值。因此需要进行补充。 大功告成。
二叉查找树的删除完整代码
对于case3,需要更加详细的说明:
10.测试程序:
1)我们首先测试插入是否正确:
我们插入的数据为{12,1,9,2,0,11,7,19,4,15,18,5,14,13,10,16,6,3,8,17} 依次插入上述内容,其得到的二叉查找树为
2)测试程序为:
二、红黑树
正如序言中所讲,红黑树是一种特殊的二叉查找树,其特殊的地方在于它的5条性质。当插入一个结点时,我们设置该结点的颜色为红色,这就可能会破坏一个红结点必须有两个黑色孩子结点的性质。当删除一个结点时,假如删除的是黑色结点,就会破坏性质5(对每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑结点。)
首先红黑树是一种二叉查找树,因此其插入与删除必然是二叉查找树的插入与删除;其次又因此其性质决定,我们必须对插入或删除的结点进行一些必要的操作,使其满足红黑树的性质。
概括的讲红黑树的插入和删除为: 二叉查找树的插入(或删除)+相应的修正
在第一部分二叉查找树中我们讲述了二叉查找树的插入和删除,接下来我们介绍相应的修正。
<一>.红黑树插入的步骤
1)二叉查找树插入结点node,2)我们设置该结点为红色。(为什么不是黑色呢?如果设置为黑色,就会破坏性质5)3)修正。
<二>红黑树插入的修正。
我们记插入结点为node ,记其叔叔结点为temp
1)如果node为父结点或者P[node]为黑色,那么修改颜色,就可以结束修正
2) 如果node不是父结点并且node为红色,那么就需要进入循环进行修正node
概述:
一共有六种情况,分为两大类,每一类各有3中情况。两大类根据红色冲突结点是左结点还是右结点进行话。每一类的3种情况,根据其叔叔结点的颜色以及插入结点node是左右结点 ,分为三种情况。
根据叔叔结点颜色,分为case1 , case2+case3
根据左右结点,分为case2 ,case3
关于case2 和case3的区别
case2:node和P[node]不是同为左节点或者同为右结点。举例:P[node]为P[P[node]]的左结点,然而node为P[node]的右结点,二者不同为左节点或者同为右结点,因此为case2.反之,如果同为左节点或者同为右结点,就是case3.
详细说明:
case1:如果node的叔叔结点temp为红色,那么修改temp 和P[node],即P[P[node]] 的颜色,同时修改node为P[P[node]]。(为什么修改P[P[node]]的颜色,因为将temp 和P[node]变为黑色时,我们必须减少该子树上的黑色结点,才能使其满足性质5)。
case2:不改动颜色,只需要通过旋转将其转化为case3
case3:改变p[temp]及P[P[temp]]的颜色,通过旋转即可结束修正。
修正 完整代码:
<三>红黑树删除的修正
红黑树的删除比较复杂,在下面文章中我先是概述了整个过程,然后又详细描述了四种情况,对于每种情况都以图说话。
我所建议的学习过程为:必须先理解理论,然后再进行编程。
理论部分:首先阅读下文的概述,然后再阅读详细内容,详细内容讲了为什么要如此变化以及变化的目的以及如何变化三部分。其中如何变化主要通过代码的形式给出。再次有条件的话,还希望去阅读以下算法导论中关于这部分内容的介绍。最后,检测你是否真的明白了整个红黑树的删除,那么你需要完成http://blog.csdn.net/v_JULY_v/article/details/6284050文章对于红黑树的删除过程。
概述:
接着二叉查找树的删除,真正删除的结点记为subst,取代subst的结点记为temp。temp为双黑结点
1)如果 被删除的结点subst为红色,则不需要进行修正
2)如果temp为根结点或者temp为红色,那么对其染黑即可。
3)如果temp不是根结点并且temp是黑色,那么需要进入循环进行修正
如同插入,分为两大类,每一类有四种情况。两大类之间通过temp是左节点还是右结点进行划分。四种情况根据temp的兄弟结点颜色和侄子结点颜色进行划分。
case1:temp的兄弟结点颜色为红色
temp的兄弟结点颜色为黑色
case2:temp的两个侄子结点都为黑色
case3:temp的远侄子结点为黑色,(那么其近侄子结点肯定是红色)
case4:temp的远侄子结点为红色。
远侄子结点的概念:
详细描述:
1)如果 被删除的结点subst为红色,则不需要进行修正
2)如果temp为根结点或者temp为红色,那么对其染黑即可。
3)两大类的区分条件
case1:双黑结点temp的兄弟结点为红色时,我们需要通过旋转将temp的兄弟结点变为黑色,从而将其转化为case2、case3、case4这三种情况。我们对temp的父结点进行旋转,通过旋转后,temp的兄弟结点w的一个子节点变为双黑结点的新的兄弟结点,新的兄弟结点为黑色。同时,我们必须在旋转的同时,进行变色,不然会影响性质5.下图给出了case1只是旋转不变色以及case1正确时的两种情况。
概括为:旋转+变色,侄子结点变兄弟。
case2:两个侄子结点均为黑色,我们修改temp的兄弟结点w的颜色为红色,从而减少了w这个分支上的黑色结点的数目,然后我们将双黑结点temp向根结点移动,双黑结点temp变为temp->parent。
特别注意:这儿只是移动,并没有对temp->parent进行变色。
case3:对其近侄子结点及父结点进行处理,变色+旋转变为case4。
case4:变色+旋转,通过变色+旋转,减少多树上的一个黑色结点,同时为两边都分配一个黑色结点,从而满足性质5
<四>红黑树删除的实际操作
我们需要对树进行删除,删除顺序如图所示:
下面的过程只是给出了删除到18的过程,后面的过程不知道被我自己弄到什么地方了,后面的过程,请参考JULY文章中的内容。
本题目是对于http://blog.csdn.net/v_JULY_v/article/details/6284050中提到的那个红黑树的删除过程,我也给出了我自己的一个比较详细的步骤,供大家参考。
<五>红黑树删除的总结
<六>红黑树删除的完整代码
学习方法:我主要是参考算法导论以及Nginx中rbtree.h和rbtree.c两部分内容来学习红黑树的。网上有很多关于红黑树的介绍,不可否认,有很多文章讲的很详细,但是我想经典毕竟是经典,去阅读算法导论,将会使你更加明白红黑树的原理。一句话,读算法导论,学红黑树。 0.序 一.二叉查找树 1.性质 2.叶子结点的表示方法: 3.程序中所使用的数据结构: 4.操作之查找 5.操作之某个结点的孩子中最小的孩子结点 6.创建结点 7.遍历二叉树(中序遍历) 8.操作之插入结点 9.操作之删除结点 10.测试程序: 二、红黑树 <一>.红黑树插入的步骤 <二>红黑树插入的修正。 <三>红黑树删除的修正 <四>红黑树删除的实际操作 <五>红黑树删除的总结 <六>红黑树删除的完整代码 |
红黑树是块硬骨头。其硬在红黑树必须满足那5条性质。其中4)红结点必须有两个黑孩子结点 5)对每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑结点。这两个性质是在插入与删除中会被破坏的性质。插入破坏性质4,删除破坏性质5.
红黑树的5条性质:
1)每个结点要么是红的,要么是黑的。 2)根结点是黑的。 3)每个叶结点,即空结点(NIL)是黑的。 4)如果一个结点是红的,那么它的俩个儿子都是黑的。 5)对每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑结点。 |
一.二叉查找树
1.性质
学习一个数据结构,必须先明白它的一些性质。对于二叉查找树,其性质:
结点的左子树总是小于
该结点;结点的右子树总是大于该结点。
2.叶子结点的表示方法:
在算法导论中,叶子结点的表示是用一个结点nil[T]来表示。所有的叶子结点都指向这个结点nil[T],该结点为哨子结点,与普通结点具有相同的域。如下图所示,下图是借用算法导论中红黑树的图来进行说明。
注意:叶子结点的描述对于后续的操作十分重要
3.程序中所使用的数据结构:(这儿使用的红黑树中的数据结构,去掉color成员变量即可)
1)结点结构,包括key,left,right,parent,color
2)树的结构,包含根结点root,叶子节点sentinel,以及插入函数insert(这个是在红黑树中使用)
typedef struct rbtree_node_s rbtree_node_t; typedef struct rbtree_s rbtree_t; typedef void (*insert_node_to_tree_pt)( rbtree_node_t * temp,rbtree_node_t * sentinel,rbtree_node_t * node); struct rbtree_node_s { rbtree_node_t * left; rbtree_node_t * right; rbtree_node_t * parent; int key; int color;/*color = 1,red;color = 0,black*/ }; struct rbtree_s{ rbtree_node_t * root; rbtree_node_t * sentinel; insert_node_to_tree_pt insert; }; |
对于二叉查找树的查找而言,十分简单。
查找可以采用递归的做法,但是递归调用函数,会造成很多资源的浪费。采用非递归方式来进行查找。
rbtree_node_t * search_node( rbtree_node_t *node ,rbtree_node_t * sentinel,int key) { rbtree_node_t * temp = node; for(;;){ if(temp == sentinel){ break; } if(temp->key < key) temp = temp->right ; else if (temp->key > key) temp = temp->left ; if(temp->key == key) break; } return temp; } |
对于二叉查找树而言,其孩子结点中最小的孩子结点,肯定是其最左下角的孩子结点。
rbtree_node_t * minimum( rbtree_node_t * node,rbtree_node_t * sentinel) { rbtree_node_t *temp = node; for(;temp-> left != sentinel;){ temp = temp-> left; } return temp; } |
rbtree_node_t * rbtree_create_node( int key) { rbtree_node_t * newnode = NULL; newnode = ( rbtree_node_t *)malloc(sizeof( rbtree_node_t)); if(NULL == newnode) return NULL; memset(newnode,0,sizeof(rbtree_node_t)); newnode-> key = key; return newnode; } |
void rbtree_traverse( rbtree_node_t *node,rbtree_node_t * sentinel) { if(node == sentinel) return ; rbtree_traverse(node-> left,sentinel); printf("key : %d color :%d\n" ,node->key,node-> color); rbtree_traverse(node-> right,sentinel); } |
二叉查找树的插入结点A比较简单,只要找到叶子结点,用结点A替换掉这个叶子结点即可。特别注意的是指针间的变化。
即插入结点为node。切记一定要对插入结点node的指针进行修改。
首先,必须考虑的是空树的情况,因为如果是空树,我们必须修改rbtree_t中root指针。
if(rbtree->root == rbtree->sentinel){ /*empty tree*/ rbtree-> root = node; node ->parent = rbtree->sentinel; node->left = rbtree-> sentinel; node->right = rbtree-> sentinel; return ; } |
for(;;){/*no-empty tree*/ if(temp->key < node->key) pp = &temp->right; else if (temp->key > node-> key) pp = &temp->left; else{ printf("existing node\n" ); return; } if(*pp == rbtree-> sentinel) break; temp = * pp; } |
/*modify pointer*/ if(temp->key < node->key) temp-> right = node; else temp-> left = node; node-> parent = temp; node-> left = rbtree->sentinel ; node-> right = rbtree->sentinel; |
void insert_node_to_tree( rbtree_t *rbtree,rbtree_node_t * node) { rbtree_node_t ** pp; rbtree_node_t * temp = rbtree->root ; if(rbtree->root == rbtree->sentinel){ /*empty tree*/ rbtree-> root = node; node -> parent = rbtree-> sentinel ; node-> left = rbtree-> sentinel ; node-> right = rbtree-> sentinel ; return ; } /*find the location where the node will insert into * temp is used to store the node which is the last non-leaf node * the location is the leaf child of temp * */ for(;;){/*no-empty tree*/ if(temp->key < node->key) pp = &temp->right ; else if (temp->key > node-> key) pp = &temp->left ; else{ printf("existing node\n" ); return; } if(*pp == rbtree->sentinel ) break; temp = *pp; } /*modify pointer*/ if(temp->key < node->key) temp-> right = node; else temp-> left = node; node-> parent = temp; node-> left = rbtree->sentinel ; node-> right = rbtree->sentinel ; return ; } |
我们记删除的结点为node,实际被删除的结点为subst,要取代该删除结点subst的结点为temp,叶子结点为sentinel
1)删除分为几种情况:
case1:删除结点node的两个孩子均为叶子结点sentinel,那么subst就是node,temp为叶子结点,直接删除即可。
case2:删除结点node只有一个孩子child,那么subst就是node,temp就是该孩子结点child,更改其指针即可。
case3:情况稍微复杂。删除结点node有两个孩子结点lchild 和rchild,我们要寻找rchild这颗子树上最小的结minnode,那么subst就是minnode。 令node的key的取值等于minnode的key的取值,然后删除subst(也就是minnode)即可,情况转化为case1 or case2。
2)我们将上面这三种情况进一步说明:
上述三种情况中,有以下几个共性和个性点。共性1:都是实际删除结点subst的父结点的孩子指针 =取代subst的结点 temp。共性2:都要修改temp的父结点指针(即使temp为叶子结点),temp的父结点指针即为subst的父结点指针。通过共性1和共性2,实现了这一对结点中parent指针和left、right指针的对应,这种对应关系,在二叉树中是十分重要的。(算法导论中关于叶子结点的说明:对于一颗红黑树T来说,哨兵nil[T]是一个与树内普通结点有相同域的对象。它的color域为BLACK,而它的其他域-p,left,right
and key 可以设置成任意允许的值。)
终结版说明:
我们可以将上述三种情况概括为一种通用的方式。我们会发现上述三种情况都是对真正要删除的结点进行处理。
(1)首先寻找真正要删除的结点subst和要取代subst的结点temp。对于case1 and case2 而言,subst就是node,temp就是subst的某个子节点;而对于case3而言,subst是node右子树的最小结点,temp就是subst的右结点(因为如果subst有左节点,那么subst就不是node的右子树的最小结点了)。因此只要找到subst,即可找到结点temp。
/*find subst and temp*/ if(node-> left == sentinel){ subst = node; temp = subst-> right; }else if(node->right == sentinel){ subst = node; temp = subst-> left; }else{ subst = rbtree_min_node(node-> right,sentinel); temp = subst-> right; } |
/*modify the pointer*/ //modify the parent pointer of child temp->parent = subst-> parent; //modify the child pointer of parent if (subst-> parent == rbtree->sentinel ) /*must add the correction of subst is root node */ rbtree-> root = temp; if(subst == subst-> parent->left ) subst-> parent->left = temp; else subst-> parent->right = temp; |
/*if it is case 3,change the key of node*/ if(subst != node){ node-> key = subst->key ; } free(subst); |
void rbtree_delete( rbtree_t *rbtree,rbtree_node_t *node) { /* * description : subst :the real deleted node * temp : the node which will replace subst after subst is deleted; * */ rbtree_node_t * temp,*subst; //step 1: find the location of subst and temp . if(node-> left == rbtree->sentinel ){ subst = node; temp = subst-> right; }else if(node->right == rbtree->sentinel){ subst = node; temp = subst-> left; }else{ subst = rbtree_min_node(node-> right,rbtree->sentinel ); temp = subst-> right; /*it is different from nginx rbtree ,because I think there is no left child for subst,so temp is sentinel or temp is subst->right child*/ } //step 2:replace node with subst temp-> parent = subst->parent ; if(subst-> parent ==rbtree->sentinel) /*must add the correction of subst is root node */ rbtree-> root = temp; else if(subst == subst->parent ->right) subst-> parent->right = temp; else subst-> parent->left = temp; //step 3: change the value of node with the content of subst if(subst != node){ node-> key = subst->key ; } free(subst); } |
对于case3,需要更加详细的说明:
10.测试程序:
1)我们首先测试插入是否正确:
我们插入的数据为{12,1,9,2,0,11,7,19,4,15,18,5,14,13,10,16,6,3,8,17} 依次插入上述内容,其得到的二叉查找树为
2)测试程序为:
#include "rbtree.h" int main( void) { rbtree_t *rbtree; rbtree_node_t *node = NULL,*sentinel = NULL; rbtree_node_t *del_node = NULL; int key_array[] = {12,1,9,2,0,11,7,19,4,15,18,5,14,13,10,16,6,3,8,17}; int i; /*begin initial*/ sentinel = ( rbtree_node_t*)malloc (sizeof( rbtree_node_t)); rbtree = ( rbtree_t *)malloc(sizeof( rbtree_t)); if(NULL == sentinel || NULL ==rbtree) return -1; node_black(sentinel); rbtree->root = sentinel; rbtree->sentinel = sentinel; rbtree->insert = insert_value; /*end initial*/ for(i = 0; i < sizeof(key_array)/sizeof(int); i++){ node = rbtree_create_node(key_array[i]); #if BST insert_node_to_tree(rbtree,node); #else rbtree_insert_node( rbtree, node); #endif } rbtree_traverse(rbtree-> root,rbtree->sentinel ); for(i = 0; i < sizeof(key_array)/sizeof(int); i++){ del_node = rbtree_search_key( rbtree,key_array[i]); if(del_node == rbtree->sentinel ){ printf("there is no key\n" ); return -1; } // rbtree_delete(rbtree,del_node);/*binary search tree delete*/ #if BST rbtree_delete(rbtree,del_node); #else rbtree_fixup_delete(rbtree,del_node); #endif printf("after delete node %d\n" ,key_array[i]); rbtree_traverse(rbtree-> root,rbtree->sentinel ); } return 0; } |
正如序言中所讲,红黑树是一种特殊的二叉查找树,其特殊的地方在于它的5条性质。当插入一个结点时,我们设置该结点的颜色为红色,这就可能会破坏一个红结点必须有两个黑色孩子结点的性质。当删除一个结点时,假如删除的是黑色结点,就会破坏性质5(对每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑结点。)
首先红黑树是一种二叉查找树,因此其插入与删除必然是二叉查找树的插入与删除;其次又因此其性质决定,我们必须对插入或删除的结点进行一些必要的操作,使其满足红黑树的性质。
概括的讲红黑树的插入和删除为: 二叉查找树的插入(或删除)+相应的修正
在第一部分二叉查找树中我们讲述了二叉查找树的插入和删除,接下来我们介绍相应的修正。
<一>.红黑树插入的步骤
1)二叉查找树插入结点node,2)我们设置该结点为红色。(为什么不是黑色呢?如果设置为黑色,就会破坏性质5)3)修正。
<二>红黑树插入的修正。
我们记插入结点为node ,记其叔叔结点为temp
rbtree_node_t ** root = &rbtree->root; |
if((node == *root) || (node_is_black(node-> parent))) node_black(*root); |
while((node != *root) && (node_is_red(node-> parent ))) |
一共有六种情况,分为两大类,每一类各有3中情况。两大类根据红色冲突结点是左结点还是右结点进行话。每一类的3种情况,根据其叔叔结点的颜色以及插入结点node是左右结点 ,分为三种情况。
根据叔叔结点颜色,分为case1 , case2+case3
根据左右结点,分为case2 ,case3
关于case2 和case3的区别
case2:node和P[node]不是同为左节点或者同为右结点。举例:P[node]为P[P[node]]的左结点,然而node为P[node]的右结点,二者不同为左节点或者同为右结点,因此为case2.反之,如果同为左节点或者同为右结点,就是case3.
详细说明:
case1:如果node的叔叔结点temp为红色,那么修改temp 和P[node],即P[P[node]] 的颜色,同时修改node为P[P[node]]。(为什么修改P[P[node]]的颜色,因为将temp 和P[node]变为黑色时,我们必须减少该子树上的黑色结点,才能使其满足性质5)。
temp = node->parent ->parent-> right; if(node_is_red(temp)){ /*case 1*/ node_black(node-> parent); node_black( temp); node_red(node-> parent->parent ); node = node-> parent->parent ; } |
if(node == node->parent ->right){ /*case 2*/ node = node-> parent; rbtree_left_rotate( root,rbtree-> sentinel, node); } |
if(node == node->parent ->right){ /*case 2*/ node = node-> parent; rbtree_left_rotate( root,rbtree-> sentinel, node); } |
while((node != *root) && (node_is_red(node-> parent))){ if(node->parent == node->parent-> parent->left ){ temp = node-> parent->parent ->right; if(node_is_red(temp)){/*case 1*/ node_black(node->parent); node_black(temp); node_red(node-> parent->parent ); node = node-> parent->parent ; } else{ if(node == node->parent ->right){ /*case 2*/ node = node-> parent; rbtree_left_rotate( root,rbtree-> sentinel, node); } /*case 3*/ node_black(node->parent); node_red(node-> parent->parent ); rbtree_right_rotate(root,rbtree-> sentinel, node->parent ->parent); } } /*parent of node is the left of parent of parent of node */ else{ temp = node-> parent->parent ->left; if(node_is_red(temp)){/*case 1*/ node_black(node->parent); node_black(temp); node_red(node-> parent->parent ); node = node-> parent->parent ; } else{ if(node == node->parent ->left){ /*case 2*/ node = node-> parent; rbtree_right_rotate( root,rbtree-> sentinel, node); } /*case 3*/ node_black(node->parent); node_red(node-> parent->parent ); rbtree_left_rotate(root,rbtree-> sentinel, node->parent ->parent); /*left rotate must be temp->parent or node->parent->parent*/ } } /*parent of node is the right of parent of parent of node */ }/*while*/ node_black (*root); |
<三>红黑树删除的修正
红黑树的删除比较复杂,在下面文章中我先是概述了整个过程,然后又详细描述了四种情况,对于每种情况都以图说话。
我所建议的学习过程为:必须先理解理论,然后再进行编程。
理论部分:首先阅读下文的概述,然后再阅读详细内容,详细内容讲了为什么要如此变化以及变化的目的以及如何变化三部分。其中如何变化主要通过代码的形式给出。再次有条件的话,还希望去阅读以下算法导论中关于这部分内容的介绍。最后,检测你是否真的明白了整个红黑树的删除,那么你需要完成http://blog.csdn.net/v_JULY_v/article/details/6284050文章对于红黑树的删除过程。
概述:
接着二叉查找树的删除,真正删除的结点记为subst,取代subst的结点记为temp。temp为双黑结点
1)如果 被删除的结点subst为红色,则不需要进行修正
2)如果temp为根结点或者temp为红色,那么对其染黑即可。
3)如果temp不是根结点并且temp是黑色,那么需要进入循环进行修正
如同插入,分为两大类,每一类有四种情况。两大类之间通过temp是左节点还是右结点进行划分。四种情况根据temp的兄弟结点颜色和侄子结点颜色进行划分。
case1:temp的兄弟结点颜色为红色
temp的兄弟结点颜色为黑色
case2:temp的两个侄子结点都为黑色
case3:temp的远侄子结点为黑色,(那么其近侄子结点肯定是红色)
case4:temp的远侄子结点为红色。
远侄子结点的概念:
详细描述:
1)如果 被删除的结点subst为红色,则不需要进行修正
red = node_is_red(subst); if(1 == red){ printf("delete red node\n" ); return ; } |
while ((temp != rbtree->root) && (node_is_black( temp)) ) 不进入该while 循环 node_black (temp); |
if (temp == temp->parent-> left){/* temp is left node*/ |
概括为:旋转+变色,侄子结点变兄弟。
if(node_is_red(w)){ /*it is essential to change color*/ node_black(w); node_red(temp-> parent); rbtree_left_rotate(&rbtree-> root,rbtree->sentinel ,temp->parent); /*rotate temp->parent*/ w = temp->parent ->right; } |
case2:两个侄子结点均为黑色,我们修改temp的兄弟结点w的颜色为红色,从而减少了w这个分支上的黑色结点的数目,然后我们将双黑结点temp向根结点移动,双黑结点temp变为temp->parent。
特别注意:这儿只是移动,并没有对temp->parent进行变色。
if(node_is_black(w->right ) && node_is_black(w->left)){ /*case 2*/ node_red(w); temp = temp->parent ; } |
case3:对其近侄子结点及父结点进行处理,变色+旋转变为case4。
if(node_is_black(w->right)){ node_black(w->left); node_red(temp->parent); rbtree_right_rotate(&rbtree->root,rbtree->sentinel,w); w = temp->parent->right; } |
case4:变色+旋转,通过变色+旋转,减少多树上的一个黑色结点,同时为两边都分配一个黑色结点,从而满足性质5
copy_node_color(w,temp-> parent); node_black(temp-> parent); node_black(w-> right); rbtree_left_rotate(&rbtree-> root,rbtree->sentinel ,temp->parent); temp = rbtree->root; |
<四>红黑树删除的实际操作
我们需要对树进行删除,删除顺序如图所示:
下面的过程只是给出了删除到18的过程,后面的过程不知道被我自己弄到什么地方了,后面的过程,请参考JULY文章中的内容。
本题目是对于http://blog.csdn.net/v_JULY_v/article/details/6284050中提到的那个红黑树的删除过程,我也给出了我自己的一个比较详细的步骤,供大家参考。
<五>红黑树删除的总结
<六>红黑树删除的完整代码
void rbtree_fixup_delete( rbtree_t *rbtree,rbtree_node_t *node) { /* * description : subst :the real deleted node * temp : the node which will replace subst after subst is deleted; * */ rbtree_node_t * temp,*subst; rbtree_node_t * w; int red = 0; //step 1: find the location of subst and temp . if(node-> left == rbtree->sentinel ){ subst = node; temp = subst-> right; }else if(node->right == rbtree->sentinel){ subst = node; temp = subst-> left; }else{ subst = rbtree_min_node(node-> right,rbtree->sentinel ); temp = subst-> right; /*it is different from nginx rbtree ,because I think there is no left child for subst,so temp is sentinel or temp is subst->right child*/ } //step 2:replace node with subst // if(temp != rbtree->sentinel)/*for rbtree delete*/ temp-> parent = subst->parent ; if(subst-> parent == rbtree->sentinel ) /*must add the correction of subst is root node */ rbtree-> root = temp; else if(subst == subst->parent ->right) subst-> parent->right = temp; else subst-> parent->left = temp; //step 3: change the value of node with the content of subst if(subst != node){ node-> key = subst->key ; } red = node_is_red(subst); if(1 == red){ printf("delete red node\n" ); return ; } while((temp != rbtree-> root) && (node_is_black(temp)) ){ if(temp == temp->parent ->left){ /*temp is left node*/ w = temp-> parent->right ; if(node_is_red(w)){/*case1: the brother node of( temp) is red;through next steps,we change the case from 1 to 2 or 3 or 4*/ /*it is essential to change color*/ node_black(w); node_red(temp-> parent); rbtree_left_rotate(&rbtree-> root,rbtree->sentinel ,temp->parent); /*rotate temp->parent*/ w = temp->parent->right ; } if(node_is_black(w->right ) && node_is_black(w->left)){ /*case 2*/ node_red(w); temp = temp->parent; } else { if(node_is_black(w->right )){/*case 3 : the far nephew of temp node is black.through next steps,we change the case from 3 to 4*/ node_black(w-> left); node_red(temp->parent ); rbtree_right_rotate(&rbtree-> root,rbtree->sentinel ,w); w = temp->parent ->right; } /*case 4 :the far nephew of temp node is red*/ copy_node_color(w,temp-> parent); node_black(temp-> parent); node_black(w-> right); rbtree_left_rotate(&rbtree-> root,rbtree->sentinel ,temp->parent); /*rotate temp->parent*/ temp = rbtree->root; } } else {/* temp is right node*/ w = temp-> parent->left ; if(node_is_red(w)){/*case1: the brother node of( temp) is red;through next steps,we change the case from 1 to 2 or 3 or 4*/ /*it is essential to change color*/ node_black(w); node_red(temp-> parent); rbtree_right_rotate(&rbtree-> root,rbtree->sentinel ,temp->parent); /*rotate temp->parent*/ w = temp-> parent->left ; } if(node_is_black(w->right ) && node_is_black(w->left)){ /*case 2*/ node_red(w); temp = temp-> parent; } else { if(node_is_black(w->left )){/*case 3 : the far nephew of temp node is black.through next steps,we change the case from 3 to 4*/ node_black(w-> right); node_red(temp-> parent); rbtree_left_rotate(&rbtree-> root,rbtree->sentinel ,w); w = temp->parent->left ; } /*case 4 :the far nephew of temp node is red*/ copy_node_color(w,temp->parent ); node_black(temp->parent ); node_black(w->left ); rbtree_right_rotate(&rbtree-> root,rbtree->sentinel ,temp->parent); /*rotate temp ->parent*/ temp = rbtree->root ; } } }/*while*/ node_black(temp); } |
相关文章推荐
- 二叉查找树与红黑树原理和程序全面介绍
- iOS -程序启动原理和UIApplication的介绍
- 红黑树之 原理和算法详细介绍
- 还为安装IIS发愁吗?全系列IIS自动安装程序倾囊奉送!--技术原理介绍及成品下载
- 介绍事务与分布式事务原理与实践、事务在Java程序中的应用
- 全面介绍ZooKeeper原理及使用
- 红黑树原理和算法详细介绍
- 红黑树(一)之 原理和算法详细介绍
- 红黑树 原理和算法详细介绍(Java)
- 红黑树(一)之 原理和算法详细介绍
- 【算法】 红黑树(一)之 原理和算法详细介绍
- 树结构(二) - 红黑树的原理介绍
- 红黑树(一)之 原理和算法详细介绍
- 红黑树原理和算法详细介绍
- 韩顺平_轻松搞定网页设计(html+css+javascript)_第19讲_js运行原理_js开发工具介绍_js程序(hello)_js基本语法_学习笔记_源代码图解_PPT文档整理
- 【spine】原理介绍和程序实现
- 用python + hadoop streaming 编写分布式程序(一) -- 原理介绍,样例程序与本地调试
- 用python + hadoop streaming 分布式编程(一) -- 原理介绍,样例程序与本地调试
- 用python + hadoop streaming 分布式编程(一) -- 原理介绍,样例程序与本地调试
- 【转】红黑树(一)原理与算法详细介绍