最近学习红黑树,个人觉得,作为一名程序员,一些经典的算法非常值得研究,他的思想他的操作对自己都是一个很好地补充学习。不过学习红黑树之前,必须要先学会二叉查找树,红黑树操作=二叉查找树操作+为符合红黑树性质所做的特殊修正。
本文为红黑树学习前篇,内容主要参考:http://blog.csdn.net/yukid2012/article/details/40479067。
自己做了一些修改,添加了一些自己的理解的注释,都是为了让读者更快更清楚的了解内容,少一些雨里雾里的绕绕。
1,性质:
二叉查找树:或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
叶子节点:在算法导论中,叶子结点的表示是用一个结点nil[T]来表示。
如下图:
![](https://img-my.csdn.net/uploads/201212/08/1354972858_2131.jpg)
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,插入结点操作:
首先找到插入位置,然后修改插入节点的对应指针,最后做特殊情况的特殊处理。特殊情况就是空树的情况。
操作过程如下图:
![](https://img-my.csdn.net/uploads/201212/08/1354973041_2919.jpg)
//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,操作如下图:
![](https://img-my.csdn.net/uploads/201212/08/1354973129_7686.jpg)
![](https://img-my.csdn.net/uploads/201212/08/1354973232_3814.jpg)
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;
}