红黑数学习

1. 红黑树的原则

1.每个节点要么是黑色,要么是红色

2.根节点必须是黑色

3.每个空的叶节点是黑色(NIL或者NULL)是黑色

4.新插入的节点是红色

5.红色节点不能连续(也就是,红色节点的孩子和父亲都不能是红色)(这个原则主要用于节点插入时树的调整)

6.每个节点,从任一节点到其nuLL的任何路径,都含有相同个数的黑色节点(也就是相同的黑色节点高度)



原则五的意思是任何一个子树,到null的黑色节点是相同的。(这个原则用于节点的删除时,对树的调整)


2. 红黑树的结构调整

红黑树的结构调整,主要是通过左旋或者右旋进行的。

2.1 左旋

被旋转的节点记为x,左旋就是将x节点的右孩子与x节点交换,左旋就是将x节点变为左节点。分为三步完成。

        (1) x节点的右孩子(xr)的左孩子(xrl)将变成x节点的右孩子(xr)

(2)x节点的右孩子(xr)成为x节点的父亲的孩子(也就是x与xr的交换)

(3)x节点成为其原右孩子的左孩子。





static void rotate_left(struct entry ** root, struct entry * x)
{
	struct entry * r = NULL;

	if((NULL != *root) &&(NULL != x)) {
		r = x->right; //1.保存x节点的右子树
		x->right = r->left;//2.x节点的右孩子换为其原来右子树的左孩子
		if(NULL != r->left)
			r->left->parent = x;//3.将x右子树的左孩子的父节点设为x ,这一步完成了
		//2,3两步完成了将x节点右子树的左孩子换为x节点的右子树
		
		r->parent = x->parent;//4.将x节点的父节点设为其右子树的父节点
		if(x->parent == NULL) {//5.说明x节点是跟节点,进行相关操作
			x->is_root = false;
			r->mod_count = x->mod_count;
			r->e_size = x->e_size;
			if(x->cmp != NULL)
				r->cmp = x->cmp;
			//上面5行完成了将根节点交接到r节点(x节点的右孩子)
			r->is_root = true;//设置r为根节点
			//r->color = BLACK;
			*root = r;
		} else if(x->parent->right == x) //6.x节点是右孩子,那么就将r节点设为x父节点的右孩子
			x->parent->right = r;
		else 
			x->parent->left = r;//7.x节点是左孩子,那么就将r节点设为x父节点的左孩子
		//4,5,6,7 这几步,主要完成x父节点与r的连接
		
		r->left = x;//8.r的左孩子为x
		x->parent = r;//9x的parent为r
		//8,9 这两个,完成将x节点设置为r节点的左孩子
		
	}
	
}

2.2 右旋

右旋跟左旋对应,右旋将x节点与x节点的左孩子交换,x节点将变为右节点。

(1)x节点的左孩子的右孩子(XLR)变为x的左孩子

(2)x节点的左孩子成为x节点父亲节点的孩子(也就是x节点与xL进行交换)

(3)x节点成为x原来左孩子的右孩子






static void rotate_right(struct entry ** root, struct entry * x)
{
	struct entry * l = NULL; 
	if((NULL != *root) && (NULL != x)) {
		l = x->left;//1.保存x节点的左子树,记为l
		x->left = l->right;//2.x节点的左孩子(l)的右孩子换为x的左孩子
		if(NULL !=l->right) 
			l->right->parent = x;//3.x节点的左孩子(l)的右孩子的父节点换为x
		//2,3步完成将x的左孩子的右孩子换为x的左孩子
		
		l->parent = x->parent;//4.x节点的左孩子的父节点,换为x的父节点
		if(NULL == x->parent) {//5.父节点为根节点
			x->is_root = false;
			l->mod_count = x->mod_count;
			l->e_size = x->e_size;
			if(x->cmp != NULL)
				l->cmp = x->cmp;
			l->is_root = true;
			//l->color = BLACK;
			*root = l;
		} else if (x->parent->right == x)//6.x是右孩子,则x左孩子成为x父节点的右孩子
			x->parent->right = l;
		else 
			x->parent->left = l;//7.x是左孩子,x左孩子成为x父节点的左孩子
		//4,5,6,7 几步完成了将x节点与x左孩子的对接
		
		l->right = x;//8.x成为x左孩子的右孩子
		x->parent = l;//9.x节点的父节点设置为x左孩子
		//8,9两步完成了将x设置为其左孩子的右孩子
	}
	
}

3.节点的插入

节点插入,新加入的节点是红色,所以如果父节点是红色的话,违反红黑树的原则5(红色节点不得连续),这时需要根据情况进行相关节点的颜色调整或者树的结构进行调整。

有以下的三种情况。

        情况一:被插入的节点是根节点

        处理方法:直接把此节点涂为黑色


情况二:被插入的节点的父节点是黑色

处理方法:什么都不需要做,因为插入是红色,父节点是黑色,仍然满足红黑树


情况三:被插入的节点的父节点是红色

处理方法:颜色调整或结构调整

调整的原则是:先调颜色后调结构

从树末(新加入点)到树根的调整,即先把先加入的父父节点为根的小树调整为满足规则,在按相同的规则逐一往上进行调整。

1).叔父节点为红色时,进行颜色调整(将父辈(父亲和叔父),以及祖父辈的颜色翻转),此时小树满足颜色要求,就将新加入的x节点转为为祖父节点,查看祖父节点所在的小树是否满足红黑颜色调整。

2)颜色调整的结束条件是叔父节点为黑色,此时颜色已经满足要求,调整树的结构来达到平衡(满足其他的要求)

3)结构调整的原则

A.x节点与父节点有相同的结构属性(比如都是左孩子或者都是右孩子):进行一种旋转

需要根据x节点的结构属性(左或者右)对x的祖父节点进行旋转

B.x节点与父节点的结构属性不相同(x是左,p是右):进行两种旋转

根据x的结束属性的相反值,对父节点进行旋转,并且x转为x的父节点

进行A情况的旋转。



  上图从截取:http://www.cnblogs.com/CarpenterLee/p/5503882.html

static void fix_after_insertion(struct entry ** root, struct entry * x)
{
	struct entry * p = NULL;
	struct entry * pp = NULL;
	struct entry * ppr = NULL;
	struct entry * ppl = NULL;
	while((NULL != x) && (!(x->is_root)) && (x->parent->color == RED)) {
		p = x->parent; //父节点
		pp = p->parent;//父父节点
		ppr = pp->right;//父父节点的右孩子
		ppl = pp->left;//父父节点的左孩子

		if(p == ppl) {//父节点是父父节点的左孩子
			if((NULL != ppr) && (ppr->color == RED)) {//情况一:叔父节点为红色时,将上两极的颜色翻转,x换为pp
				p->color = BLACK;	//父节点设置为黑色
				ppr->color = BLACK;	//叔父节点设置为黑色
				pp->color = RED;	//父父节点设置为红色
				x = pp;	//x转为父父节点
			} else {//叔父节点为空,或者为黑色
				if(x == p->right) {//情况二:x与p的结构属性不同,按照p进行左旋
					x = p;
					rotate_left(root, x);//进行左旋
				}
				//情况三:按照祖父节点进行右旋
				x->parent->color = BLACK; //父节点为黑色
				x->parent->parent->color = RED;//父父节点为红色
				rotate_right(root, x->parent->parent);//进行右旋
			}
		} else {//父节点是父父节点的右孩子
			if((ppl != NULL) && (ppl->color == RED)) {//叔父节点为红色时,将上两极的颜色翻转
				p->color = BLACK;//父节点设为黑色
				ppl->color = BLACK;//叔父节点设为黑色
				pp->color = RED;//父父节点设为红色
				x = pp;//x换为父父节点
			} else {//叔父节点为黑色
				if(x == p->left) {//x是左孩子,需要进行右旋
					x = p;
					rotate_right(root, x);
				}
				x->parent->color = BLACK;//x的父节点为黑色
				x->parent->parent->color = RED;//x的父父节点为红色
				rotate_left(root, x->parent->parent);//x的父父节点左旋
			}
		}
	}
	
	(*root)->color = BLACK;//设置跟节点为黑色
}
static void * put(struct entry ** root, void * key, void * data)
{
	struct entry * t = *root;
	struct entry * tt = *root;
	struct entry * r = NULL;
	struct entry * x;
	int cmp = 0;
	if(NULL == t) {//根节点为空,创建一颗树
		printf("is null!\n");
		r = entry_new(key, data);
		r->is_root = true;
		r->color = BLACK;
		r->mod_count++;
		r->e_size++;
		*root = r;
		return NULL;
	}
	//这个比较函数没有通过函数的方式进行赋值,可以再根节点创建后进行赋值
	if(NULL ==(*root)->cmp)
		(*root)->cmp = default_cmp;
	//如果键值已经存在,则重置键值	
	do {	
		x = t;
		cmp = (*root)->cmp(key,x->key);
		if(cmp > 0)
			t = t->right;
		else if (cmp < 0)
			t = t->left;
		else
			return set_value(t, data);
	} while(t != NULL);
	//键不存在在新建节点
	r = entry_new(key, data);
	//r->root = *root;
	if(cmp < 0)
		x->left = r;
	else 
		x->right = r;
	r->parent = x;
	
	tt->mod_count++;
	r->e_size++;
	
	fix_after_insertion(root, r);
	return NULL;
	
}


4.获得最小或最大节点

因为红黑树是一颗有序树,树的左边总是小于树的右边。因此只要找到左节点,就是整颗树的最小值,找到整颗树的最右边,就是整棵树的最大值。

/*
*	寻找最左边的孩子,也就是整棵树的最小值的节点
*/
static struct entry * entry_node_first(struct entry * root)
{
	struct entry * nl;
	nl = root;
	if(!nl)
		return NULL;
	while(nl->left)
		nl = nl->left;
	return nl;
}
/*
*	寻找最右边的孩子,也就是整棵树的最大值的节点
*/
static struct entry * entry_node_last(struct entry * root)
{
	struct entry * nr;
	nr = root;
	if(!nr)
		return NULL;
	while(nr->right)
		nr = nr->right;
	return nr;
}

5.节点的删除

节点删除后,容易造成黑色节点的高度不同。因此节点删除时树结构调整的总原则是:如果被删除的节点是黑色,需要进行调整。

           节点的删除有以下几种情况:被删除的节点记为D

(1)被删除节点没有孩子(也就是叶子节点),被删除的叶子节点是黑色时,进行树结构的调整,然后直接将该节点删除

         (2)被删除节点只有一个孩子,那么直接删除该节点,并用该节点的唯一子节点替换它。如果替换的节点是黑色,进行树结构的调整

          (3)被删除节点有两个孩子,先找到该节点的替换节点(被删除节点的左子树的最左节点),此时被删除的节点变为了替换节点(替换节点的键和值拷贝到被删除的节点,需要将替换的节点删除掉),如果替换节点为黑色,进行树结构的调整。

/*
*	找到删除节点的继承者
*	原则:当删除节点存在左和右节点的时候,需要寻找
		且继承者是其右孩子或者右孩子的最左孩子
*	从删除函数中可知,找到的后继者肯定都是右子树的最左孩子
*/
static struct entry * successor(struct entry * x)
{
	struct entry * p= NULL;
	struct entry * ch = NULL;
	if(NULL == x)
		return NULL;
	else if(x->right != NULL) {//1.x 的右子树不为空,则t的后继是其右子树中最小的那个元素
		p = x->right;
		while(p->left != NULL)
			p = p->left;
		return p;
	} else {//2. x的右子树为空,则t的后继是其第一个向左走的祖先
		p = x->parent;
		ch = x;
		while(p != NULL && ch == p->right) {
			ch = p;
			p = p->parent;
		}
		return p;
	}
}


static void delete_entry(struct entry ** root, struct entry * p)
{
	struct entry *s = NULL;
	struct entry *replace = NULL;
	struct entry * r = *root;
	struct entry * node = *root;
	r->e_size--; //树中节点的个数
	r->mod_count++;//树的修改次数
	printf("tree->key:%ld, tree->data:%ld, is_root:%d, size:%d, color:%d\n", (long)p->key, (long)p->data, p->is_root, (*root)->mod_count, p->color);
	if((p->left != NULL) && (p->right != NULL)) {//(3).删除点p的左右子树都非空,这个情况会最终转换为删除叶子节点
		s = successor(p);//继承者是右孩子的最左子孙
		//使用继承者替换要删除的节点
		p->key = s->key;
		p->data = s->data;
		//上面两步是找到继承者之后,替换key和value
		p = s;//将继承者赋值为p,进行后续的处理
		printf("tree->key:%ld, tree->data:%ld, is_root:%d, size:%d, color:%d\n", (long)p->key, (long)p->data, p->is_root, (*root)->mod_count, p->color);
	}
	//如果p有左孩子则取左孩子,否则取右孩子
	replace = (p->left != NULL ? p->left : p->right);
	
	if(replace != NULL) {//(2).删除点只有一个子树非空 

		
		replace->parent = p->parent;
		if(p->parent == NULL) {//p节点没有父节点,则p是父节点
			p->is_root = false;
			replace->mod_count = p->mod_count;
			replace->e_size = p->e_size;
			if(p->cmp != NULL)
				replace->cmp = p->cmp;
			replace->is_root = true;
			replace->color = BLACK;
			*root = replace;
		} else if(p = p->parent->left) {//删除p的父节点到p的引用
			p->parent->left = replace;
		} else 
			p->parent->right = replace;
		p->left = p->right = p->parent = NULL;//解除p的左右节点以及父节点的引用
		if(p->color == BLACK)
			fix_after_deletion(root, replace);
	} else if(p->parent == NULL) {//(4):如果一个节点没有左右节点以及父节点,则说明是根节点,也就是只有一个节点
			*root = NULL;
	} else {//(1)被删除的节点是叶子节点

		if (p->color == BLACK)
            fix_after_deletion(root, p); //根据
		
		if(p->parent != NULL) { 
			if(p == p->parent->left)
				p->parent->left = NULL;
			else if(p == p->parent->right)
				p->parent->right = NULL;
			p->parent= NULL;
		}
	} 
	
	if(p)
		free(p);	
}

删除时的结构调整,有如下几种情况

1)兄弟节点是红色(说明父节点肯定黑色,且兄弟的孩子都是黑色)

2)兄弟节点是黑色,且兄弟节点的两个子节点都是黑色

3)兄弟节点是黑色,且兄弟节点的左子节点为红色,右子节点为黑色

4)兄弟节点是黑色,且兄弟节点的右子节点为红色,左子节点任意色

2)3)4)三种情况是兄弟节点为黑色时的子集。

情况一:当前节点是黑色,且兄弟节点是红色(此时父节点和子节点肯定是黑色的)

处理办法:将父节点和兄弟节点颜色翻转,并按照父节点进行旋转(旋转的方向与当前节点的结构属性相同(左孩子向左,右孩子向右)),调整之后,树变有了2)3)4)的属性特点了



情况二:兄弟节点是黑色,且兄弟节点的子节点都是黑色

处理办法:将兄弟节点颜色翻转,然后将当前节点设为父节点,开始新一轮的颜色检查



情况三:兄弟节点为黑色,它的左子节点为红色(或者不存在),它的右子节点为黑色。(此情况调整之后,必然要走情况四)

处理办法:A.将兄弟节点及其孩子的颜色翻转;B.按照兄弟节点进行旋转(旋转的方向与当前节点的结构属性相反(当前节点为左节点,则右旋,反之亦然))C.旋转后,从新设置兄弟节点


           


情况四:兄弟节点是黑色,兄弟孩子的右孩子(左孩子)是红色,左孩子任意

处理办法:

      A.兄弟节点的颜色为父节点颜色

B.父节点以及右节点(或左节点)设置为黑色

        c.按照父节点进行旋转(旋转方法与节点的结构属性相同(左节点往左旋,反之亦然))

        d.当前节点设置为根节点(意味着结束调整)



从以上的特点可以看出,如果是情况一,那么可能出现2,3,4的情况。如果是情况二,那么需要进行新一轮的情况检查后并做相关的操作。如果是情况3,那么必然出现情况四,如果二和三都不是,那么必然是四。

static void fix_after_deletion(struct entry ** root, struct entry * x)
{
	struct entry * node = NULL;
	while(x && (x != *root) && (x->color == BLACK)) {
		if(x == x->parent->left) { //x是左孩子
			node = x->parent->right;
			if(node && node->color == RED) {//如果右孩子是红色,此时父节点肯定是黑色
			/*
			*	删除节点是黑色,兄弟节点为红色
			*	兄弟节点和父节点的颜色翻转
			*	将父节点进行左旋
			*	交换后x的父节点不变,但是其父节点的右孩子变为父节点原来右孩子的左孩子
			*/
				node->color = BLACK;			
				x->parent->color = RED;
				rotate_left(root, x->parent);
				node = x->parent->right;
			}
			
			if(node->left && node->left->color == BLACK && node->right && node->right->color == BLACK) {
				/*
				*	此时,x为黑,x的兄弟为黑,x的兄弟的黑子都是黑
				*	将x的兄弟设为红色
				*	x转移到x的父节点
				*/
				node->color = RED;
				x = x->parent;
			} else {
				if(node->right && node->right->color == BLACK) {
					/*
					*	此时左边为红色,右边为黑色
					*	将左右两边的颜色翻转
					*	右旋后
					*/
					if(node->left)
						node->left->color = BLACK;
					node->color = RED;
					rotate_right(root, node);
					node = x->parent->right;
				}
				node->color =x->parent->color;
				x->parent->color = BLACK;
				node->right->color = BLACK;
				rotate_left(root, x->parent);
				x = *root;
			}
		} else {
			node = x->parent->left;
			if(node && node->color == RED) {
				node->color = BLACK;
				x->parent->color = RED;
				rotate_right(root, x->parent);
				node = x->parent->left;
			}
			if(node->right && node->right->color == BLACK && node->left && node->left->color == BLACK) {
				node->color = RED;
				x = x->parent;
			} else {
				if(node->left && node->left->color == BLACK) {
					if(node->right)
						node->right->color = BLACK;
					node->color = RED;
					rotate_left(root, node);
					node = node->parent->left;
				}
				
				node->color = x->parent->color;
				x->parent->color = BLACK;
				node->left->color = BLACK;
				rotate_right(root, x->parent);
				x = *root;
			}
		}
	}
	x->color = BLACK;
}


图截取于http://www.cnblogs.com/CarpenterLee/p/5525688.html

6.树的遍历

 红黑树是二叉平衡的有序树,因此可以采用从根节点开始分为左边遍历以及右边遍历,也就是找到树的最小节点或者最大节点,然后根据下一节点进行遍历。

/*
*	查找左边的下一个
*/
static struct entry * entry_node_next(struct entry * root)
{
	struct entry * parent;
	struct entry * r = root;

	if(!root->parent) {//如果是根节点,返回
		return NULL;
	}
	
	if(r->right) {//
		r = r->right;
		while (r->left)
			r = r->left;
		return r;		
	}
	
	while((parent = r->parent) && r == parent->right)//从最后一个叶子节点找到根节点
		r = parent;
	
	return parent;
}
/*
*	查找右边的下一个
*/
static struct entry * entry_node_prev(struct entry * root)
{
	struct entry *parent;
	struct entry * node = root;

	if(!root->parent)
		return NULL;
	
	if(node->left) {
		node = node->left;
		while(node->right)
			node = node->right;
		return node;
	}
	
	while((parent = node->parent) && node == parent->left)
		node = parent;
	return parent;
}

遍历函数

 

static void print_the_tree(struct entry * root)
{
	struct entry * node = NULL;
	//左边的树
	for(node = entry_node_first(root); node != root; node = entry_node_next(node)) {
		printf("node->key:%ld, node->data:%ld, is_root:%d, color:%d\n", (long)node->key, (long)node->data, node->is_root, node->color);
	}
	//右边的树
	for(node = entry_node_last(root); node != root; node = entry_node_prev(node)) {
		printf("node->key:%ld, node->data:%ld, is_root:%d, color:%d\n", (long)node->key, (long)node->data, node->is_root, node->color);
	}
	printf("root->key:%ld, root->data:%ld, is_root:%d, size:%ld, color:%d\n", (long)root->key, (long)root->data, root->is_root, root->e_size, root->color);
}

 
















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值