红黑树的插入(算法导论)

  红黑树是一棵二叉搜索树,它在每个节点增加了一个存储位来表示节点的颜色。一颗红黑树是满足下面红黑性质的二查搜索树:

1)每个节点或是红色的,或是黑色的

2)根节点是黑色的

3)每个叶节点(NIL)是黑色的

4)如果一个节点是红色的,则它的两个子节点都是黑色的

5)对每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。

         对一颗有n个内部节点的红黑树,其高度至多为2lg(n+1)。这样可以保证红黑树的查找的时间复杂度控制在O(lgn)。

         对每一颗红黑树,我们假如所有的叶子节点都为空,并且都是黑色的。而任何非空节点都是内部节点,并且只能是红色或黑色。

 1、红黑树的插入

         在红黑树的插入中,默认插入的节点是红色的,因为假如插入的节点为黑色的,则会导致红黑树的黑高不等,若是红色的则不会出现此问题,但是这时有可能出现插入节点的父节点也是红色的,这样就破坏了红黑树的第四条性质。这时就需要对红黑树进行调整。

 

 红黑树的插入总共六种情况,其中三种与另外三种对称。假如要插入的节点为z,则

在此我们主要介绍z的父节点是其祖父节点的左孩子的情况。这种情况下又有三种情况,分别为:

         1)z的叔叔节点y是红色的。这时将z的父节点和z的叔叔节点y都修改成黑色的,z的祖父节点A原来一定是黑色的,这时将它修改为红色的,并将新的z节点指向z的祖父节点,然后继续向上进行调整。


图1.1 z的叔叔节点为红色的情况

 

         2)z的叔叔节点是黑色的,并且z是一个右孩子节点。此时,首先将z指针指向其父节点,然后以此时z指向的节点为中心进行左旋,于是将此种情况转化成了Case 3的情形。即z节点是左孩子节点。

图1.2 z的叔叔节点为黑色并且z为右孩子


         3)z的叔叔节点是黑色的,并且z是一个左孩子节点。此时首先将z的父节点修改为黑色,然后将其祖父节点修改为红色,然后以z的祖父节点为中心进行右旋。

 

图1.3 z的叔叔节点为黑色并且z为左孩子


在调整结束后,需要将根节点的颜色重新更改为黑色

关于红黑树的创建,插入以及调整的C代码如下:

#include <stdio.h>
#include <stdlib.h>
typedef struct Node
{
	int key, color;					//color = 0:red, 1:black
	struct Node *left, *right, *p;
}NODE, *TREE;

TREE rb_insert_fixup(TREE r, NODE *z)
{
	TREE y;
	y = NULL;
	while(z->p!=NULL && z->p->color == 0)
	{
		if(z->p == z->p->p->left)	//z的父节点是其祖父节点的左孩子, 此种情形有三种情况 
		{
			y = z->p->p->right;
			if(y!=NULL && y->color == 0)	//case1: z的叔叔节点为红色 
			{
				z->p->color = 1;
				y->color = 1;
				z->p->p->color = 0;
				z = z->p->p;
			}
			else
			{
				if(z == z->p->right)	//case2: z的叔叔节点为黑色,并且z是右孩子 ,此时左旋后变成case3的情形 
				{
					z = z->p;
					r = left_rotate(r, z);
				}
									//case3: z的叔叔节点为黑色,并且z是左孩子 
				z->p->color = 1;
				z->p->p->color = 0;
				r = right_rotate(r, z->p->p);
			}
		}
		else					//z的父节点是其祖父节点的右孩子,此种情形也有三种情况,并且与上面三种情况对称 
		{
			y = z->p->p->left;
			if(y->color == 0)
			{
				z->p->color = 1;
				y->color = 1;
				z->p->p->color = 0;
				z = z->p->p;
			}
			else
			{
				if(z == z->p->left)
				{
					z = z->p;
					r = right_rotate(r, z);
				}
				z->p->color = 1;
				z->p->p->color = 0;
				r = left_rotate(r, z->p->p);
			}
		}
	}
	r->color = 1;
	return r;
}

TREE rb_insert(TREE r, NODE *z)
{
	TREE x, y;
	x = r;
	y = NULL;
	while(x != NULL)
	{
		y = x;
		if(z->key < x->key)
			x = x->left;
		else 
			x = x->right;
	}
	
	z->p = y;
	if(y == NULL)
	{
		r = z;
	}
	else if(z->key < y->key)
	{
		y->left = z;
	}
	else 
	{
		y->right = z;
	}
	z->left = NULL;
	z->right = NULL;
	z->color = 0;
	
	r = rb_insert_fixup(r, z);
	return r;
}

TREE rb_create(int arr[], int n)
{
	TREE r, tmp;
	int i;
	r = NULL;
	for(i=0; i<n; i++)
	{
		tmp = (TREE)malloc(sizeof(NODE));
		tmp->key = arr[i];
		r = rb_insert(r, tmp);
	}
	return r;
}
void print_pre(TREE r)
{
	if(r == NULL)
		return ;
	if(r->color == 0)
		printf("%d:red  ", r->key);
	else
		printf("%d:black  ", r->key);
	print_pre(r->left);
	print_pre(r->right);
}
void print_mid(TREE r)
{
	if(r == NULL)
		return ;
	print_mid(r->left);
	if(r->color == 0)
		printf("%d:red  ", r->key);
	else
		printf("%d:black  ", r->key);
	print_mid(r->right);
}
int main()
{
	TREE r;
	int n = 6, arr[6]={41, 38, 31, 12, 19, 8};
	//create a rb tree
	r = rb_create(arr, n);
	
	printf("----------先序遍历----------:\n");
	print_pre(r);
	
	printf("\n\n----------中序遍历----------:\n");
	print_mid(r);
	printf("\n\n----------End line----------\n");
	
	return 0;
}

 2、旋转

现在我们介绍一下刚才用到的旋转,旋转有两种:左旋和右旋。其左旋和右旋过程分别图下图所示。

图2.1 右旋

右旋的C代码如下:

TREE right_rotate(TREE r, NODE *x)
{
	TREE y = x->left;
	x->left = y->right;
	if(y->right != NULL)
		y->right->p = x;
	y->p = x->p;
	if(x->p == NULL)
		r = y;
	else if(x->p->left = x)
		x->p->left = y;
	else
		x->p->right = y;
	y->right = x;
	x->p = y;
	
	return r;
}



图2.2 左旋

左旋的C代码如下:

TREE left_rotate(TREE r, NODE *x)
{
	TREE y = x->right;
	x->right = y->left;
	if(y->left != NULL)
		y->left->p = x;
	y->p = x->p;
	if(x->p == NULL)
		r = y;
	else if(x == x->p->left)
		x->p->left = y;
	else 
		x->p->right = y;
	y->left = x;
	x->p = y;
	
	return r;
}


posted @ 2014-11-01 22:40  liuwu265  阅读(848)  评论(0编辑  收藏  举报