算法导论例程——红黑树

红黑树是比较重要的数据结构,作为一个典型的平衡二叉树(没有一条简单路径是其他路径的二倍)。它与二叉搜索树的区别是它的结点多了一个属性——color,color有两种值,red和black,颜色的选取遵循以下原则:

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

2)根节点是黑色的;(松弛红黑树 relaxed red-black tree的根节点可以是红色)

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

4)如果一个节点是红色的,那么他的两个子节点都是黑色的;

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

#define BLACK 0;
#define RED 1;
using namespace std;
typedef struct red_black_tree
{
	int num;
	red_black_tree* p;
	red_black_tree* left;
	red_black_tree* right;
	int color;
}RBT;


可见性质中有很多都是用黑节点来定义的,所以为了描述方便还引进了一个概念——黑高bh(x),指的是从节点x到叶节点的任一简单路径的黑节点的数目。

由此可以推得,以x为根的子树至少包含2^(bh(x)) - 1个内部节点,推广到整棵树,得到有n个节点的红黑树其高度不超过2lg(n+1)。

搜索树的操作insert和delete在红黑树上的最坏运行时间为o(lgn),由于这两种操作对树的结构做出了改变,我们需要一个机制来维护红黑树的性质,由于红黑树是有color属性的,所以简单的trans操作不适用,我们引进新的基本操作——旋转。

红黑树的旋转分为左旋和右旋,以左旋为例:对节点x的左旋,假设x有右孩子y,那么旋转之后x成为y的左孩子,y的左孩子成为x的右孩子,y代替了x的位置。这样做不会改变二叉搜索树的性质。

要提示一点的是,红黑树中不允许出现“没有颜色”的空节点,因此本节代码中所有的NULL都应替换为以下声明的一个NIL哨兵节点,它是一种哑元节点,对速度没有什么影响。

RBT NIL;
{
	NIL.left = NULL;
	NIL.right = NULL;
	NIL.color = BLACK;
}


void left_rotate(RBT* root, RBT* key)
{
	RBT* key_r;
	if (key->right != NULL)
		key_r = key->right;
	else
		return -1;                       //没有右孩子无法左旋

	if(key_r->left != NULL)
		key_r->left->p = key;
	key->right = key_r->left;
	

	if (key->p == NULL)
	{
		key_r->p = NULL;
		root = key_r;
	}
	else if (key == key->p->left)
		key->p->left = key_r;
	else
		key->p->right = key_r;
	key_r->p = key->p;
		
	key_r->left = key;
	key->p = key_r;
}

void rigth_rotate(RBT* root, RBT* key)
{
	RBT* key_l;
	if (key->left != NULL)
		key_l = key->left;
	else
		return -1;                       //没有左孩子无法右旋

	if (key_l->right != NULL)
		key_l->right->p = key;
	key->left = key_l->right;


	if (key->p == NULL)
	{
		key_l->p = NULL;
		root = key_l;
	}
	else if (key == key->p->left)
		key->p->left = key_l;
	else
		key->p->right = key_l;
	key_l->p = key->p;

	key_l->right = key;
	key->p = key_l;
}

有了左旋和右旋作为基础,我们就可以考虑对红红黑树的插入操作了。首先我们要明确的一点是——红黑树依然是二叉搜索树,因此我们可以仿照二叉搜索树的插入方法来写出 红黑树的插入,这样可以保证我们的插入操作不会使树违反二叉搜索树的性质。

void RB_insert(RBT* root, RBT* key)
{
	RBT *x, *y;
	x = root;
	while (x != NULL)
	{
		y = x;
		if (key->num < x->num)
			x = x->left;
		else
			x = x->right;
	}
	key->p = y;
	if (y == NULL)
		root = key;
	else if (key->num < y->num)
		y->left = key;
	else
		y->right = key;

	key->left = NULL;
	key->right = NULL;
	key->color = RED;
	RB_insert_fix(root, key);
}

唯一的不同就是我们把新插入的节点着色为红色,之所以着为红色是因为这样只会使它违反红黑树的性质2和性质4,方便我们后期的维护。

接下来呢我们讨论关键字节点的父节点、祖父节点和叔叔节点的颜色对其进行讨论,其中case2和case3是具有关联性的,case2完成旋转之后一定会成为case3。具体的讨论参见书,这里就不画图了。

case1: key的叔叔节点是红色

case2:key的叔叔节点是黑色的且key是一个右孩子

case3:key的叔叔节点是黑色且key是一个左孩子

void RB_insert_fix(RBT* root, RBT* key)
{
	RBT *y;
	while (key->p->color == RED)
	{
		if (key->p == key->p->p->left)
		{
			y = key->p->p->right;                           //uncle node
			if (y->color == RED)
			{
				key->p->color = BLACK;
				y->color = BLACK;
				key->p->p->color = RED;
				key = key->p->p;
				//case 1:  father node is RED,uncle node is RED
			}
			else if (key == key->p->right)
			{
				key = key->p;
				left_rotate(root, key);
				//case 2: father node is RED,but uncle node is black
			}
			key->p->color = BLACK;
			key->p->p->color = RED;
			right_rotate(root, key->p->p);
			//case 3: father node is RED,uncle node is BLACK,key is the left child
		}
		else
		{
			y = key->p->p->left;
			if (y->color == RED)
			{
				key->p->color = BLACK;
				y->color = BLACK;
				key->p->p->color = RED;
				key = key->p->p;
			}
			else if (key == key->p->left)
			{
				key = key->p;
				right_rotate(root, key);
			}
			key->p->color = BLACK;
			key->p->p->color = RED;
			left_rotate(root, key->p->p);
		}
	}
	root->color = BLACK;
}

红黑树放弃了追求完全平衡,追求大致平衡,在与平衡二叉树的时间复杂度相差不大的情况下,保证每次插入最多只需要三次旋转就能达到平衡,实现起来也更为简单。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值