红黑树学习前篇

最近学习红黑树,个人觉得,作为一名程序员,一些经典的算法非常值得研究,他的思想他的操作对自己都是一个很好地补充学习。不过学习红黑树之前,必须要先学会二叉查找树,红黑树操作=二叉查找树操作+为符合红黑树性质所做的特殊修正。
本文为红黑树学习前篇,内容主要参考:http://blog.csdn.net/yukid2012/article/details/40479067
自己做了一些修改,添加了一些自己的理解的注释,都是为了让读者更快更清楚的了解内容,少一些雨里雾里的绕绕。

1,性质:
二叉查找树:或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;

叶子节点:在算法导论中,叶子结点的表示是用一个结点nil[T]来表示。
如下图:

 
2,数据结构:
typedef struct rbtree_node_s rbtree_node_t;
typedef struct rbtree_s rbtree_t;

//表示某一节点的结构
struct rbtree_node_s {
     rbtree_node_t   * left;		
     rbtree_node_t   * right;
     rbtree_node_t   * parent;
     int             key;
};

//表示树的结构
struct rbtree_s{
    rbtree_node_t           * root;
    rbtree_node_t           * sentinel;
};

 

3,查找操作:
查找可以采用递归的做法,但是递归调用函数,会造成很多资源的浪费。采用非递归方式来进行查找。

//node:传入的根节点,sentinel:叶节点,key:查询的值
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;
}

 

4,查找某个结点的孩子中最小的孩子结点操作:
对于二叉查找树而言,其孩子结点中最小的孩子结点,肯定是其最左下角的孩子结点,不清楚的话参考性质.

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;
}

 

5,创建结点操作:
创建节点需要malloc一段内存,然后置空后赋值,最后返回创建节点的地址。

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;
}


6,遍历二叉树操作
二叉查找树采用的中序遍历,递归调用。

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);
}


7,插入结点操作:
首先找到插入位置,然后修改插入节点的对应指针,最后做特殊情况的特殊处理。特殊情况就是空树的情况。
操作过程如下图:7,插入结点操作:
首先找到插入位置,然后修改插入节点的对应指针,最后做特殊情况的特殊处理。特殊情况就是空树的情况。
操作过程如下图:

 
//node节点要插入树rbtree中
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){ 
                                rbtree-> root = node;
                    node -> parent = rbtree-> sentinel ;
                    node-> left   = rbtree-> sentinel ;
                    node-> right  = rbtree-> sentinel ;

                                 return ;
                }
                 /*查找要插入的节点位置
                 * */
                 for(;;){
                                
                                 if(temp->key < node->key)
                                                pp = &temp->right ;
                                 else if (temp->key > node-> key)
                                                pp = &temp->left ;
                                 else{
                                                 printf("existing node\n" );
                                                 return;
                                }
				
				//此处退出循环是,temp为查找位置节点的父节点。	
                                 if(*pp == rbtree->sentinel )
                                                 break;
                                temp = *pp;
                }

                 /*修改相应指针*/
		 //首先找出应该插入的左右位置。
                 if(temp->key < node->key)
                                temp-> right = node;             
                 else
                                temp-> left = node;
		//修改插入节点的父、左、右节点指针。
                node-> parent = temp;
                node-> left = rbtree->sentinel ;
                node-> right = rbtree->sentinel ;
                 return ;

}

 

8,删除结点操作:
声明:删除的结点为node,实际被删除的结点为subst,要取代该删除结点subst的结点为temp,叶子结点为sentinel。
8.1,删除的场景:
 case1:删除结点node的两个孩子均为叶子结点sentinel,那么subst就是node,temp为叶子结点,直接删除即可。
 case2:删除结点node只有一个孩子child,那么subst就是node,temp就是该孩子结点child,更改其指针即可。
 case3:删除结点node有两个孩子结点lchild 和rchild,我们要寻找rchild这颗子树上最小的结minnode(原因:找到最小的顶替node可保持二叉查找树的性质不变,依然是右子节点大于父节点,父节点大于左子节点),那么subst就是minnode。 令node的key的取值等于minnode的key的取值,然后删除subst(也就是minnode)即可,情况转化为case1 or case2。
8.2,删除的步骤:
(1)首先寻找真正要删除的结点subst和要取代subst的结点temp。
(2)修改subst和temp相应的指针。如果删除的根结点,那么还需要修改rbtree_t中的root指针。
(3)是否需要修改node的key值。
8.3,操作如下图:

 
void rbtree_delete( rbtree_t *rbtree,rbtree_node_t *node)
{

    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;	//不可能左子节点,有的话最小节点就应该是左子节点。
    }

    //step 2:replace node with subst 
    //指针处理:需要更改temp节点的父节点指向,subst的父节点的左右子节点指向。
    temp-> parent = subst->parent ;
    if(subst-> parent ==rbtree->sentinel) 
                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);
}


9,完整的测试程序操作(不一定正确,部分代码稍加改动即可,太晚了直接复制别人):

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值