浅谈红黑树(C语言代码实现)
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅谈红黑树(C语言代码实现)相关的知识,希望对你有一定的参考价值。
定义:
我们先来看看《算法导论》中的红黑树的定义:“红黑树是许多‘平衡’搜索树的一种,可以保证在最坏的情况下基本动态集合操作的时间复杂度为O(lgn)。”
性质:
红黑树的性质如下:
1、每个节点是红色的,或者是黑色的。
2、根节点和叶子节点是黑色的。
3、如果一个节点是红色的,那么它的父节点和子节点必须是黑色的。
4、对于每一个节点来说,从该节点到叶子节点的简单路径上,所包含的黑色节点的数量必须一致。
节点属性:
红黑树每个节点的基本属性值有五个:
1、color:表示该节点的颜色属性。
2、data:表示该节点的数据域。
3、parent:表示该节点的父节点。
4、left_child:表示该节点的左子结点。
5、right_child:表示该节点的右子结点。
以下代码的红黑树节点属性信息:
int data ;
int color ;
struct rbnode *left_child ;
struct rbnode *right_child ;
struct rbnode *parent ;
} Rbnode;
typedef Rbnode *Rbtree;
插入操作:
插入操作包含以下三个步骤:
1、查找操作。为待插入节点寻找合适的位置。
红黑树的节点的数据域的规律满足平衡二叉树的节点数据与的规律,也就是某节点的左子结点的数据域小于该节点的数据域,同时该节点的右子结点的数据域大于该节点的数据域。
根据以上数据域的规律可以找到带插入节点的位置,若待插入节点的数据域已经存在于树中,可以选择继续插入,也可以直接返回,该处,我选择若存在就直接返回,若该节点的数据域在该树中不存在,就返回查找到的待测节点的父节点。
代码如下:
static Rbnode *rbtree_search_auxilary(Rbtree root, int data, Rbnode **parent)
{
Rbnode *node = NULL, *sparent;
if(NULL == root){
return NULL;
}
node= root;
while(node){
sparent = node->parent;
if(data == node->data){
return node;
}else if(data > node->data){
node = node->right_child;
}else{
node = node->left_child;
}
}
if(parent){
*parent = sparent;
}
return NULL;
}
2、插入待测节点操作。
由性质4可以得出,为了使新插入节点尽量不破坏红黑树的平衡,新插入节点的颜色必须为红色。
代码如下:
Rbtree rbtree_insert(Rbtree root, int data)
{
Rbtree tree = NULL;
Rbnode *node = NULL, *parent = NULL;
// analyse whether newnode‘s data in this tree, if yes, you need‘t insert it.
if(root){
node = rbtree_search_auxilary(root, data, &parent);
if(node){
printf("%d already in this tree\n", data);
return root;
}
}
// insert data in tree;
node = buy_node(); // function ‘buy_node‘ just malloc a space for new_node;
node->data = data;
node->color = RED;
node->parent = parent;
node->left_child = node->right_child = NULL;
if(parent){
if(parent->left_child){
parent->left_child = node;
}else{
parent->right_child = node;
}
}else{
root = node;
}
return rbtree_insert_rebalance(root, node);
}
3、平衡红黑树操作。
若新插入节点的父节点为黑色节点,那么该树满足红黑树的性质,不需要调整。若新插入节点的父节点为红色节点,此时违背了红黑树的性质3,需要进一步平衡红黑树。
平衡插入操作有五种情况:
1.父节点P,叔叔节点U,都是红色;
2.父节点P是红色,叔叔节点U是黑色或者NULL,且N为右孩子
3.父节点P是红色,叔叔节点U是黑色或者NULL,且N为左孩子
代码如下:
static Rbtree rbtree_insert_rebalance(Rbtree root, Rbnode *node)
{
Rbnode *gparent = NULL, *parent = NULL, *uncle = NULL;
if(NULL == root || NULL == node){
return NULL;
}
while((parent = node->parent) && RED == parent->color){
gparent = parent->parent;
if(gparent){
if(parent == gparent->left_child){
uncle = gparent->right_child;
// case 1,uncle is red;
if(uncle && RED == uncle->color){
parent->color = BLACK;
uncle->color = BLACK;
gparent->color = RED;
node = gparent;
}else{
/*case2 & case3, uncle is black.
case2: if new_node is parent‘s right_node, becase parent is gparent‘s
left_child, this case belongs to inner_insert, so, it is need
double_rotate, right_rotate -> left_rotate.
case 3: new_node is parent;s left_node, its belongs to outer_insert,
just need left_rotate.
accrounding foregoing analysis, case2 contains case3.*/
if(node == parent->right_child){
root = rbtree_rotate_left(root, parent);
swap(&parent, &node, sizeof(parent));
}
parent->color = BLACK;
gparent->color = RED;
root = rbtree_rotate_right(root, gparent);
}
}else{
// the same as foregoing operating.
uncle = gparent->left_child;
if(uncle && RED == uncle->color){
uncle->color = BLACK;
parent->color = BLACK;
gparent->color = RED;
node = gparent;
}else{
if(node == parent->left_child){
root = rbtree_rotate_right(root, parent);
swap(&parent, &node, sizeof(parent));
}
parent->color = BLACK;
gparent->color = RED;
root = rbtree_rotate_left(root, gparent);
}
}
}
}
root->color = BLACK;
return root;
}
由于在平衡过程中由旋转(即左旋、右旋)的过程,还有工具函数swap的使用,附上源码:
旋转的过程可能是我们最头疼的问题,因为搞不懂指针的指向,在这里,我与大家分享一下小技巧,在旋转的过程中我们一步一步来。
首先,旋转的过程中解决子节点问题。若右旋过程中,节点存在右子结点,将右子结点托管其父节点,作为父节点的左子结点,左旋则相反。
其次,旋转过程中解决父节点的问题。无论左旋还是右旋其父节点,均都会作为该旋转节点的子节点。
最后,解决节点本身的问题。节点的父节点是哪个?其父节点为该节点父节点的父节点,若父节点的父节点不存在,该节点为root。节点的子节点是哪个?若右旋过程中,父节点作为其右子结点,左子结点不变;左旋相反。
左旋右旋的代码如下:
static Rbtree rbtree_rotate_left(Rbnode *node, Rbtree root)
{
Rbnode *right = node->right_child;
// child problem
if(node->right_child = right->left_child){
right->left_child->parent = node;
}
// parent problem
if(right->parent = node->parent){
if(node == node->parent->right_child){
node->parent->right_child = right;
}else{
node->parent->left_child = right;
}
}else{
root = right;
}
// self problem
right->left_child = node;
node->parent = right;
return root;
}
static Rbtree rbtree_rotate_right(Rbnode *node, Rbtree root)
{
Rbnode *left = node->left_child;
// child‘s problem
if(node->left_child = left->right_child){
left->right_child->parent = node;
}
// parent‘s problem
if(left->parent = node->parent){
if(node == node->parent->right_child){
node->parent->right_child = left;
}else{
node->parent->left_child = left;
}
}else{
root = left;
}
// self‘s problem
left->right_child = node;
node->parent = left;
return root;
}
swap函数源码:
void swap(void *a, void *b, size_t size)
{
void *tmp;
tmp = (void *)Malloc(size);
memcpy(tmp, a, size);
memcpy(a, b , size);
memcpy(b, tmp, size);
free(tmp);
}
删除操作:
删除操作与插入操作类似也分为三步操作:
1、找出待删除节点。其查找方法与插入操作的查找节点的方法一致,不再做过多赘述。
2、若节点存在就删除该节点。
情况一:待删除节点有两个子节点。
情况二:待删除节点有一个子节点或者不存在子节点。
情况一可以转换成情况二,当待删除节点存在两个子节点时,可以用左子树的最大节点或者右子树的最小节点替换该节点,此时,只需要删除左子树的最大节点或者右子树的最小节点即可。
代码如下:
Rbtree rbtree_delete(Rbtree root, int data)
{
Rbnode *parent = NULL, *child = NULL;
Rbnode *node = NULL, *old = NULL;
int color = RED;
// find the delete-node
node = rbtree_search_auxilary(root, data, NULL);
if(!node){
fprintf(stderr, "%d does not in the tree\n");
return NULL;
}
// remember the delete-node
old = node;
/*
* case 1 : the delete-node has only-one child, you just
* let node‘s parent adapt you child;
* case 2 : the delete-node has two children, you can find
* max-left_child or min-right_child to success your
* property & support your parent.
* Then case 2 can switch to case 1.
*/
// case 2
if(node->left_child && node->right_child){
// we choose its min-right_child
node = node->right_child;
node = find_min_node(node);
// you should solve the min-right_child‘s child & parent
child = node->right_child;
parent = node->parent;
color = node->color;
// child first
if(child){
child->parent = parent;
}
// parent sencond
if(parent){
if(node == parent->left_child){
parent->left_child = child;
}else{
parent->right_child = child;
}
}else{
root = child;
}
if(node->parent == old){
parent = node;
}
// the use min-right_child to replace the delete-node
node->parent = old->parent;
node->right_child = old->right_child;
node->left_child = old->left_child;
node->color = old->color;
if(old->parent){
if(old == old->parent->left_child){
old->parent->left_child = node;
}else{
old->parent->right_child = node;
}
}else{
root = node;
}
old->left_child->parent = node;
if(old->right_child){
old->right_child->parent = node;
}
}else{
// case 1 : only has one child or has no->child
if(node->left_child){
child = node->left_child;
}else{
child = node->right_child;
}
parent = node->parent;
color = node->color;
if(child){
child->parent = parent;
}
if(parent){
if(node == parent->left_child){
parent->left_child = child;
}else{
parent->right_child = child;
}
}else{
root = child;
}
}
free(old);
if(BLACK == color){
return rbtree_delete_rebalance(root, child, parent);
}
return root;
}
3、删除节点后平衡红黑树。
若删除的节点是红色,并没有破坏红黑树的平衡,不需要做平衡操作,若删除节点的黑色节点,是通过该节点的路径比不通过该节点大的路径减少一个黑色节点,违反了红黑树的性质4。
删除操作有以下四种情况:
1.当前节点是黑色,当前兄弟节点为红色(此时父节点和兄弟节点的子节点为黑色);
2.当前节点是黑色,且当前节点的兄弟节点是黑色,并且兄弟节点的两个子节点全为黑色;
3.当前节点是黑色,兄弟节点是黑色,兄弟节点的左孩子是红色,右孩子是黑色;
4.当前节点是黑色,兄弟节点是黑色,兄弟节点的右孩子是红色,左孩子颜色任意;
具体平衡操作代码如下:
static Rbtree rbtree_delete_rebalance(Rbtree root, Rbnode *node, Rbnode *parent)
{
Rbnode *bro = NULL;
Rbnode *bro_left = NULL, *bro_right = NULL;
while((NULL == node || BLACK == node->color) && node != root){
if(node == parent->left_child){
bro = parent->right_child;
// case 1 : bro is Red
if(bro && RED == bro->color){
bro->color = BLACK;
bro->parent = RED;
root = rbtree_rotate_left(parent, root);
bro = parent->left_child;
}
if((!bro->left_child || BLACK == bro->left_child->color) &&
(!bro->right_child || BLACK == bro->right_child->color)){
// case 2 : bro is BLACK & its children are all BLACK
bro->color = RED;
node = parent;
parent = node->parent;
}else{
// case 3 : bro is BLACK & its left_child is RED & bro_right is BLACK
if(NULL == bro->right_child || BLACK == bro->right_child->color){
if(bro_left = bro->left_child){
bro_left->color = BLACK;
}
bro->color = RED;
root = rbtree_rotate_right(bro, root);
bro = parent->right_child;
}
// case 4 : bro is BLACK & its right_child is RED;
bro->color = parent->color;
parent->color = BLACK;
if(bro->right_child){
bro->right_child->color = BLACK;
}
root = rbtree_rotate_left(parent, root);
node = root;
break;
}
}else{
bro = parent->left_child;
// case 1
if(bro && RED == bro->color){
bro->color = BLACK;
bro->parent = RED;
root = rbtree_rotate_right(parent, root);
bro = parent->left_child;
}
// case 2
if((!bro->left_child || BLACK == bro->left_child->color) &&
(!bro->right_child) || BLACK == bro->right_child->color){
bro->color = RED;
node = parent;
parent = node->parent;
}else{
// case 3
if(!bro->right_child || BLACK == bro->right_child->color){
if(bro_left = bro->left_child){
bro_left->color = BLACK;
}
bro->color = RED;
root = rbtree_insert_rebalance(bro, root);
node = parent->left_child;
}
// case 4
bro->color = parent->color;
parent->color = BLACK;
if(bro->right_child){
bro->right_child->color = BLACK;
}
root = rbtree_rotate_left(parent,root);
node = root;
break;
}
}
}
if(node){
node->color = BLACK;
}
return root;
}
由于以上代码中牵扯寻找左子树的最大节点,右子树的最小节点,以下是实现源码:
Rbnode *find_min_node(Rbtree root)
{
Rbnode *node = root;
if(NULL == root){
return NULL;
}
while(node->left_child){
node = node->left_child;
}
return node;
}
Rbnode *find_max_node(Rbtree root)
{
Rbnode *node = root;
if(NULL == root){
return NULL;
}
while(node->right_child){
node = node->right_child;
}
return node;
}
以上是本人对红黑树的简单理解,如有不妥之处,欢迎大家指出。
本文出自 “11219885” 博客,请务必保留此出处http://11229885.blog.51cto.com/11219885/1746148
以上是关于浅谈红黑树(C语言代码实现)的主要内容,如果未能解决你的问题,请参考以下文章