红黑树

     这儿主要给出红黑树的代码实现,和我的一些理解。具体的红黑树介绍在算法导论的163页,也可以自己google或百度。

  红黑树简介: 

  红黑树是每个节点都带有颜色属性的二叉查找树,颜色或红色或黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:

  性质1. 节点是红色或黑色。

  性质2. 根节点是黑色。

  性质3 每个叶节点是黑色的。

  性质4 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)

  性质5. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。

  这些约束强制了红黑树的关键性质: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这个树大致上是平衡的。因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上的理论上限允许红黑树在最坏情况下都是高效的,而不同于普通的二叉查找树。
  要知道为什么这些特性确保了这个结果,注意到性质4导致了路径不能有两个毗连的红色节点就足够了。最短的可能路径都是黑色节点,最长的可能路径有交替的红色和黑色节点。因为根据性质5所有最长的路径都有相同数目的黑色节点,这就表明了没有路径能多于任何其他路径的两倍长。                                              (摘自百度百科)

 

  因为一颗空的红黑树就是一颗满足所有性质的红黑树,所以构造出一颗红黑树是简单的。重要的是如何在执行可能会破坏红黑树的的操作:插入和删除。如何红黑树继续保持这些性质。

  下面就针对插入和删除时如何保持红黑树的性质进行具体的解释和实现。

  插入和删除都要用到的基本操作是旋转。(旋转是个简单而又经典的操作!)

插入:

  插入的基本操作和二叉查找树的插入操作类似(如果不是很清楚可以到算法导论的151页学习一下,或者到我的上一篇博客看看代码),再对每个新插入的节点的color属性都赋值为红色。为什么不是黑色呢?很简单,如果是赋值为黑色,则每次插入就一定会破坏红黑树的性质。可是如果赋值为红色就不一定啦!可是,红黑树的性质依然可能被破坏,所以我们可以再每次的插入操作之后执行insertRB_fixup操作,来检查红黑树的性质是否被破坏,如果被破坏了,就通过对部分节点的调整来恢复红黑树的性质。

调整的可能情况有:

  如过插入的节点是根节点。此时因为我们每次插入的节点都是红色,会破坏性质2)。这是可以简单的将根节点的颜色改为黑色来恢复红黑树的性质。

  如果插入的节点不是根节点,且这个节点的父节点是黑节点。这时没有破坏任何性质。不需要调整。

  如果插入的节点不是根节点,且这个节点的父节点是红节点。这时性质4)被破坏。这时可以再细分为三种情况(用z表示插入的节点,且设z为左孩子):

  ①z的叔叔y是红色的。

  ②z的叔叔y是黑色的,而且z是右孩子

  ③z的叔叔y是黑色的,而且z是左孩子

  对于①因为z的父节点和叔叔节点都是红色的,则z的祖父节点是存在的且是黑色的。可以讲z的父节点和叔叔节点都有红色改为黑色,再将z的祖父节点有黑色改为红色(这样就不会破坏性质5)),现在z的父亲节点是黑色的了,所以对z来说没有破坏红黑是的性质。不过z的祖父被改为了红色,则又有可能破坏性质4)。同样的问题向上转移了。如果还是情况①,则继续向上转移。直到不再是情况①,最终的结果又四种可能:㈠变为情况②。㈡变为情况③(z指示的节点需要变化一下)。㈢在节点上移到根节点的子节点,因为根是黑的,所以问题结束。㈣z上移到根节点,这是需要将根节点重新改为黑色。

  对于②,只需对z的父节点左旋一下,即可转换为情况③。

  对于③,可以将z的颜色父节点改为黑色,再将z的祖父节点改为红色,之后再对z的祖父节点右旋转一下即可恢复性质4)。

 

删除:(首先是类似二叉树的删除,然后调整。二叉树的任意节点删除会转换为只对有nil子节点的节点的删除,具体参考二叉查找树的删除)

1.如果删除的节点是红色的,则节点删除后,红黑树的性质没有被破坏。
2.如果删除的节点y是黑色的,2)、4)性质可能被破坏。在y被删除之前,y的孩子只有两种可能的情况:①有唯一的一个非nil的子节点。②没有非nil的子节点。
用x表示删除y节点后,代替y位置的节点。可分两种情况:

Ⅰ.x为红色,则是在①的情况下,即y有一个唯一的孩子x,且x为红节点。此时只需将x的颜色由红色改为黑色,即可恢复红黑树的性质。
Ⅱ.x为黑色,又如果x是根节点,则红黑树的性质没有被破坏。如果x不是根节点,则x的父节点一定存在。又因为x是黑节点,所以此时x的父节点一定还存在另一个非nil的节点,
否则违反性质5)。
对x是黑节点,且x的父节点都存在的情况,可以再分成四类,不是一般性的,可以先假设x是父节点的left。
㈠:x的兄弟w是红色的(因为w是红色的,所以x和w的父节点是黑色的)
㈡:x的兄弟w是黑色的,而且w的两个孩子都是黑的
㈢:x的兄弟w是黑色的,而且w的左孩子是红色的,右孩子是黑色的。
㈣:x的兄弟w是黑色的,而且w色右孩子是红色的

现在对删除红黑树节点可能损坏二叉树的性质5)的解决方法进行分析。如果删除了一个黑节点且破坏了性质5),即造成了包含x节点的路径比其他路径少了一个黑的节点。解决方法
可以归纳为两种:①将包含x的节点的路径的黑节点都增加一。②将其他不包含x的路径的黑节点数都减少一。通过这两种解救方法就可以使黑节点数在此平衡。
解决的算法见算法导论的173业,当然也可以看下面我的代码。这个算法的主体部分是对上面的㈠、㈡、㈢、㈣情况的解决,其他的情况都很容易解决。

对于㈠,通过一次旋转,并对两个节点的颜色交换,可以转换为情况㈡或㈢或㈣。
对于㈢,通过一次旋转,并对两个节点的颜色交换,可以转换为请款㈣。
对于㈣,通过一次旋转,并对两个节点的颜色交换,同时再把x的兄弟节点的右子节点的颜色由红色变为黑色,就可以恢复红黑树的性质5)。由颜色的变化就可以发现这儿额外的将一个红节点改为了黑节点。所以属于通过方法①,来恢复红黑树的性质的。其实如果忽略每个节点的数据,只关心每个节点的颜色,这个方法可以简单的理解为从x的父节点的另一颗子树哪儿找到一颗红的节点,将其变为黑色,并插入x为根的那颗子树。但是,为了不破坏红黑树的数据性质,所以通过颜色的交换和节点的旋转来实现。

对于㈡,先将x的兄弟节点的颜色改为红色,这样包含x的兄弟节点的所有路径的黑节点数也都减少了一个,所以现在问题变为包含x的父亲的节点的所有路径的黑节点数都少了一个。这样可以看成x就向上移了一层。然后再判断新的x属于那种情况,如果是㈠、㈢、㈣,则问题解决,如果还是㈡,则继续上移。所以㈡的结束有两种可能,一是在某次上移之后变为了情况㈠、㈢、㈣后解决,二是,一直上移到根节点后结束。这时,所有不包括x节点的路径的黑节点的数目都减少了1,红黑树的性质5)得以恢复。这种解决方法属于解决方法②。

/* 
# Red Black Tree Algorithm  
# Copyright (C) 2010-****  Long Cheng  <fantasywindy@gmail.com> 
# @version 1.1.1-20100721
# */  
/*
性质1. 节点是红色或黑色
性质2. 根是黑色
性质3. 每个红色节点的两个子节点都是黑色 (从每个叶子到根的所有路径上不能有两个连续的红色节点)
性质4. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点
*/

#include <stdio.h>
#include <stdlib.h>
typedef enum Color   //定义红黑树结点颜色颜色类型
{
    RED = 0,
    BLACK = 1
}Color;

typedef struct Node     //定义红黑树结点类型
{
	struct Node *parent;
	struct Node *left;
	struct Node *right;
	int value;
	Color color;
}Node, *Tree;
Node *nil=NULL;           //为了避免讨论结点的边界情况,定义一个nil结点代替所有的NULL

Node* Parent(Node *z)     //返回某结点的父母
{
	return z->parent;
}
Node* Left(Node *z)       //返回左子树
{
	return z->left;
}
Node *Right(Node *z)      //返回右子树
{
	return z->right;
}
void LeftRotate(Tree &T, Node *x)     //左旋转:结点x原来的右子树y旋转成为x的父母
{
	if( x-> right != nil )
	{
		Node *y=Right(x);
		x->right=y->left;
		if(y->left != nil)
		{
			y->left->parent=x;
		}
		y->parent=x->parent;
		if( x->parent == nil )
		{
			T=y;
		}
		else
		{
			if( x == Left(Parent(x)) )
			{
				x->parent->left=y;
			}
			else
			{
				x->parent->right=y;
			}
		}
		y->left=x;
		x->parent=y;
	}
	else
	{
		printf("%s/n","can't execute left rotate due to null right child");
	}
}

void RightRotate(Tree &T, Node *x)   //右旋转:结点x原来的左子树y旋转成为x的父母
{
	if( x->left != nil )
	{
		Node *y=Left(x);
		x->left=y->right;
		if( y->right != nil )
		{
			y->right->parent=x;
		}
		y->parent=x->parent;
		if( x->parent == nil )
		{
			T=y;
		}
		else
		{
			if(x == Left(Parent(x)) )
			{
				x->parent->left=y;
			}
			else
			{
				x->parent->right=y;
			}
		}
		y->right=x;
		x->parent=y;
	}
	else
	{
		printf("%s/n","can't execute right rotate due to null left child");
	}

}

void InsertFixup(Tree &T, Node *z)     //插入结点后, 要维持红黑树四条性质的不变性
{
	Node *y;
	while( Parent(z)->color == RED )  //因为插入的结点是红色的,所以只可能违背性质3,即假如父结点也是红色的,要做调整
	{
		if( Parent(Parent(z))->left == Parent(z) )    //如果要插入的结点z是其父结点的左子树
		{
			y=Parent(Parent(z))->right;         // y设置为z的叔父结点
			if( y->color == RED )       //case 1: 如果y的颜色为红色,那么将y与z的父亲同时着为黑色,然后把z的
			{                           //祖父变为红色,这样子z的祖父结点可能违背性质3,将z上移成z的祖父结点
				y->color=BLACK;
				z->parent->color=BLACK;
				z->parent->parent->color=RED;
				z=z->parent->parent;
			}
			else 
			{
				if( z == z->parent->right )    //case 2: 如果y的颜色为黑色,并且z是z的父母的右结点,则z左旋转,并且将z变为原来z的parent.
				{
					z=z->parent;
					LeftRotate(T, z);
				}
				z->parent->color=BLACK;       //case 3: 如果y的颜色为黑色,并且z是z的父母的左结点,那么将z的
				z->parent->parent->color=RED;    //父亲的颜色变为黑,将z的祖父的颜色变为红,然后旋转z的祖父
				RightRotate(T,z->parent->parent);
			}
		}
		else               //与前一种情况对称,要插入的结点z是其父结点的右子树,注释略去
		{
			y=Parent(Parent(z))->left;
			if( y->color == RED)
			{
				z->parent->color=BLACK;
				y->color=BLACK;
				z->parent->parent->color=RED;
				z=z->parent->parent;
			}
			else
			{
				if( z == z->parent->left )
				{
					z=z->parent;
					RightRotate(T,z);
				}
				z->parent->color=BLACK;
				z->parent->parent->color=RED;
				LeftRotate(T,z->parent->parent);
			}
		}
	}
	T->color=BLACK;    //最后如果上升为T的根的话,把T的颜色设置为黑色
}
void Insert(Tree &T, int val)    //插入结点
{
	if(T == NULL)    //初始化工作:如果根尚不存在,那么new一个新结点给根,同时new一个新结点给nil
	{
		T=(Tree)malloc(sizeof(Node));
		nil=(Node*)malloc(sizeof(Node));
		nil->color=BLACK;            //nil的颜色设置为黑
		T->left=nil;
		T->right=nil;
		T->parent=nil;
		T->value=val; 
		T->color=BLACK;         //为了满足性质2,根的颜色设置为黑色
	}
	else             //如果此树已经不为空,那么从根开始,从上往下查找插入点
	{
		Node *x=T;          //用x保存当前顶点的父母结点,用p保存当前的结点
		Node *p=nil;
		while(x != nil)       //如果val小于当前结点的value值,则从左边下去,否则从右边下去
		{
			p=x;
			if(val < x->value )
			{
				x=x->left;
			}
			else if(val > x->value)
			{
				x=x->right;
			}
			else
			{
				printf("%s %d/n","duplicate value",val);   //如果查找到与val值相同的结点,则什么也不做,直接返回
				return;
			}	

		}
		x=(Node*)malloc(sizeof(Node));   
		x->color=RED;             //新插入的结点颜色设置为红色
		x->left=nil;
		x->right=nil;
		x->parent=p;            
		x->value=val;
		if( val < p->value )
		{
			p->left = x;
		}
		else 
		{
			p->right = x;
		}
		
		InsertFixup(T, x);         //插入后对树进行调整
		
	}
}

Node* Successor(Tree &T, Node *x)   //寻找结点x的中序后继
{
	if( x->right != nil )         //如果x的右子树不为空,那么为右子树中最左边的结点
	{
		Node *q=nil;
		Node *p=x->right;
		while( p->left != nil )
		{
			q=p;
			p=p->left;
		}
		return q;
	}
	else                       //如果x的右子树为空,那么x的后继为x的所有祖先中为左子树的祖先
	{
		Node *y=x->parent;
		while(  y != nil && x == y->right )
		{
			x=y;
			y=y->parent;
		}
		
		return y;
	}
}

void DeleteFixup(Tree &T, Node *x)    //删除黑色结点后,导致黑色缺失,违背性质4,故对树进行调整
{
	while( x != T && x->color == BLACK )  //如果x是红色,则直接把x变为黑色跳出循环,这样子刚好补了一重黑色,也满足了性质4
	{
		if( x == x->parent->left )     //如果x是其父结点的左子树
		{
			Node *w=x->parent->right;       //设w是x的兄弟结点
			if( w->color == RED )         //case 1: 如果w的颜色为红色的话
			{
				w->color=BLACK;
				x->parent->color=RED;
				LeftRotate(T, x->parent);
				w=x->parent->right;
			}
			if( w->left->color == BLACK && w->right->color == BLACK )   //case 2: w的颜色为黑色,其左右子树的颜色都为黑色
			{
				w->color=RED;
				x=x->parent;
			}
			else if( w->right->color == BLACK )         //case 3: w的左子树是红色,右子树是黑色的话
			{
				w->color=RED;
				w->left->color=BLACK;
				RightRotate(T, w);
				w=x->parent->right;
			}
			w->color=x->parent->color;            //case 4: w的右子树是红色
			x->parent->color=BLACK;
			w->right->color=BLACK;
			LeftRotate(T , x->parent);
			
			x=T;
		}
		else           //对称情况,如果x是其父结点的右子树
		{
			Node *w=x->parent->left;
			if( w->color == RED )
			{
				w->color=BLACK;
				x->parent->color=RED;
				RightRotate(T, x->parent);
				w=x->parent->left;
			}
			if( w->left->color == BLACK && w->right->color == BLACK )
			{
				w->color=RED;
				x=x->parent;
			}
			else if( w->left->color == BLACK )
			{
				w->color=RED;
				w->right->color=BLACK;
				LeftRotate(T, w);
				w=x->parent->left;
			}
			w->color=x->parent->color;
			x->parent->color=BLACK;
			w->left->color=BLACK;
			RightRotate(T , x->parent);
			
			x=T;
			
		}
	}
	x->color=BLACK;
}

void Delete(Tree &T, Node *z)     //在红黑树T中删除结点z
{
	Node *y;   //y指向将要被删除的结点
	Node *x;    //x指向将要被删除的结点的唯一儿子
	if( z->left == nil || z->right == nil )    //如果z有一个子树为空的话,那么将直接删除z,即y指向z
	{
		y=z;
	}
	else
	{
		y=Successor(T, z);     //如果z的左右子树皆不为空的话,则寻找z的中序后继y,
	}                          //用其值代替z的值,然后将y删除 ( 注意: y肯定是没有左子树的 )
	if( y->left != nil )         //如果y的左子树不为空,则x指向y的左子树
	{
		x=y->left;
	}
	else
	{	
		x=y->right;
	}
	x->parent=y->parent;    //将原来y的父母设为x的父母,y即将被删除
	if( y->parent == nil )     
	{
		T=x;
	}
	else
	{
		if( y == y->parent->left )   
		{
			y->parent->left=x;
		}
		else
		{
			y->parent->right=x;
		}
	}
	if( y != z )   //如果被删除的结点y不是原来将要删除的结点z,
	{              //即只是用y的值来代替z的值,然后变相删除y以达到删除z的效果
		z->value=y->value;
	}
	if( y->color == BLACK )    //如果被删除的结点y的颜色为黑色,那么可能会导致树违背性质4,导致某条路径上少了一个黑色
	{
		DeleteFixup(T, x);
	}
}
Node* Search(Tree T, int val)
{
	if( T != nil )
	{
		if( val < T->value )
		{
			Search(T->left, val);
		}
		else if ( val > T->value )
		{
			Search(T->right,val);
		}
		else
		{
			return T;
		}
	}
}

void MidTranverse(Tree T)
{
	if( T != NULL && T != nil )
	{
		MidTranverse(T->left);
		printf("%d ",T->value);
		MidTranverse(T->right);
	}

}
int main()
{
	Tree t=NULL;
	Insert(t,10);
	Insert(t,85);
	Insert(t,15);
	Insert(t,70);
	Insert(t,20);
	Insert(t,60);
	Insert(t,30);
	Insert(t,50);
	Insert(t,65);
	Insert(t,80);
	Insert(t,90);
	Insert(t,40);
	Insert(t,5);
	Insert(t,55);
	Delete(t,Search(t,30));
	Delete(t,Search(t,65));
	MidTranverse(t);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值