红黑树学习前篇

来源:互联网 发布:油性漆与水性漆 知乎 编辑:程序博客网 时间:2024/06/13 00:37

最近学习红黑树,个人觉得,作为一名程序员,一些经典的算法非常值得研究,他的思想他的操作对自己都是一个很好地补充学习。不过学习红黑树之前,必须要先学会二叉查找树,红黑树操作=二叉查找树操作+为符合红黑树性质所做的特殊修正。
本文为红黑树学习前篇,内容主要参考: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
原创粉丝点击