非递归学习树结构(六)--RB-Tree(红黑树)



终于深入的理解并用代码一步一步实现了红黑树。Linux内核的进程调度和内存管理,STL库的map容器的实现都使用了红黑树,这是一种效率很高的二叉搜索树,效率基本上满足log(n),它与AVL树有什么区别呢?


学习了AVL树之后,如果你觉得关于二叉搜索树的学习就结束了,那你就错了,因为,还有一个树,红黑树,红黑树学习完之后,二叉搜索树基本上就告一段落了,还有一些二叉搜索树的变种,如AA-Tree,在这里就不讲了,弄明白红黑树,学习AA树应该也不是太难的事情了。


可能有人要问,有了AVL树,干嘛还要红黑树呢?是啊,AVL树效率是最稳定最高的二叉搜索树,因为AVL是一颗高度平衡的二叉树,任意一个结点的左右子节点高度差不大于1,有人问,有没有高度差为0的,那我想请问你,给你两个点,你给我造出来一个高度差为0的树出来!!!


闲话不多扯,还是说说AVL树和RB-Tree的故事。AVL树是高度平衡,为了达到高度平衡,在建树和删除的时候操作较为复杂,并不是说变化的算法复杂,而是牵扯的结点较多,有时候为了在插入一个结点以后能满足AVL树的性质,整棵树都会跟着旋转变换。插入和删除的效率较低,而RB-Tree就是为了在提高插入删除效率的同时,又不会使查找效率有大的变化而诞生的。红黑树不要求高度平衡,但是其也是一颗泛平衡树,因为从根节点出发,到叶子节点的最长距离不会超过最短距离的二倍,从下面红黑树的性质就可以看出来:


1.Anode is either red or black.

一个结点非黑即红。

2.Theroot is black.

根节点是黑色

3.Allleaves (NIL) are black.

所有叶子节点是黑色

4.Ifa node is red, then both its children are black.

如果一个结点是红色,则它的孩子节点必须是黑色。

5.Everypath from a given node to any of its descendant NIL nodes contains the samenumber of black nodes.The uniform number of blacknodes in the paths from root to leaves is called the black-height of thered–black tree.

黑高相同。

 

所谓的黑高,就是指从一个节点出发,到叶子节点(NIL)所经历的黑色节点的个数

如下图所示





这里我们引入了一个NIL节点,这个NIL结点的颜色是黑色的,它并不是数据,而是作为类似于标记用的,说明已经到底了,为什么引入这个NIL节点?是为了简化删除中的调整代码,在删除的调整函数void deleteadjust(Node** root, Node* node)的注释中会有说明。


 


红黑树的难点在于插入和删除的调整上,插入和删除节点以后都要保证不会破坏上述五点红黑树特性。插入操作比较简单一些,删除操作比较复杂一点,但是,操作的思路还是比较清晰的,下面我们就结合图例还说明插入和删除的各种case,以及对应的操作方法。


插入:


红黑树规定:待插入的结点的颜色都要定义成红色。


插入操作我们分为两部分,一部分是插入,另一部分是插入后的调整。插入操作和前面BST树以及AVL树的插入一样。插入完毕后,有可能遇到以下几种情况:


Case 1




上图中N是插入的结点,可以看到,N和其父节点P都是红色,而且N的叔结点U也是红色,这就违背了性质4如果一个结点是红色,则它的孩子节点必须是黑色),调整的操作方法是:将PU都变为黑色,G变为红色,这样就满足了性质4,也不会违背性质5,完美结束。



Case 2




N结点的叔结点U如果是黑节点,操作:G改变为红色,P改变为黑色,在P出一次右旋,就OK了。


 


Case 3


3


可以看到,图2和图1有点像,只要对P进行一次左旋,左旋之后就变成了case2的情况,按照case的操作,就OK了。


后面的插入操作的代码就可以照着上图进行理解,因为插入操作比较简单,因此没有过多的说明。本文的重点放在了删除操作上,插入操作还可以结合以下两个链接的内容来理解:


https://en.wikipedia.org/wiki/Red%E2%80%93black_tree


http://www.cnblogs.com/fornever/archive/2011/12/02/2270692.html(可以理解是维基百科的中文翻译,英文好的同学就不必看了)


 


删除:


下面,我们就重点说说红黑树的删除操作,


BST以及AVL树的删除操作一样,其实是找到要删除节点的直接后继(右子树的最左子节点)或者直接前驱节点(左子树的最右子节点),然后用直接后继(或者直接前驱,后面我们都以直接后继来描述)的值替换到要删除的结点的值,然后删除直接后继节点。


如果这个直接后继节点是红色,而且孩子节点都是NIL,那就省事了,直接删除就行了,因为删除这个红节点不会破坏任何一个性质。


如果这个直接后继节点N是黑色,而他的右孩子SR是红色,那么SR的两个孩子肯定是NIL,因为SR本身是红色,随意本身不可能跟红色节点,而N的左节点是nil,这样就是的SR不能有黑色节点,否则就会使得N的左右子树的黑高不一样。调整操作是把SR染成黑色,替换掉N节点即可。


上面我们讨论了两种比较简单的情况,那么我们处理一下负责的情况,那就是如果N是一个黑色的结点,而且N的两个孩子都是NIL节点,那么删除了N以后就会破坏平衡,如下图。





删除了N以后,P的左子树的黑高是少1的,这是就需要调平,当然,P的右子节点不一定是黑色的,这里只是画出一种情况进行说明。后面涉及到的各种case,其实就是P和其右子节点颜色各种不同造成的,下面就一一分析。


case 1:


case 2:


case 3:

case 4:

case 5:

case 5的图只画出了P节点右子树,N节点是P的左子树,没有画,请自行脑补即可。


以上删除操作都是讨论的N是P的左子节点,S是P的右子节点的情况,至于N是P右子节点,S是P左子节点的情况,操作一样,只是左旋变右旋,右旋变左旋以及操作的子节点不一样,操作方法一样。



上面五种case就是能遇到的情况。这里说一下为什么增加NIL这个节点,设想一下,如果没有NIL这个节点而是用NULL,那么我们在第一次进行调整的时候就得作为特殊情况考虑,因为NULL是没有color的,然后才能用通用的调整代码,这里我们引入了NIL之后就好多了,NIL本身无数据,但是其有颜色,所以能将第一次调整操作也融入到通用代码中了。


好了,下面我们就把代码贴上:

/*RB-Tree.c*/
#include "RB-Tree.h"
static Node* NIL = NULL;

/*get grandparent*/
Node* grandparent(Node* node)
{
	return node->pp->pp;
}

/*get parent*/
Node* parent(Node* node)
{
	return node->pp;
}

/*get uncle*/
Node* uncle(Node* node)
{
	if (grandparent(node) == NULL) {
		return NULL;
	}
	return (parent(node) == grandparent(node)->plc) ? grandparent(node)->prc : grandparent(node)->plc;
}

Node* createnode(int key,int val)
{
	Node* node = (Node*)malloc(sizeof(Node));
	node->cnt = 0;
	color = RED;
	node->color = color;
	node->key = key;
	node->val = val;
	node->plc = NIL;
	node->prc = NIL;
	node->pp = NIL;

	return node;
}

/*左旋转*/
Node* Left_Rotate(Node** root, Node* node)
{
	Node* tmp = node->prc;
	node->prc = tmp->plc;
	if (tmp->plc != NULL) {
		tmp->plc->pp = node;
	}

	tmp->pp = parent(node);
	if (parent(node) == NULL) {
		*root = tmp;
	}
	else {
		if (parent(node)->plc == node) {
			parent(node)->plc = tmp;
		}
		else {
			parent(node)->prc = tmp;
		}
	}
	node->pp = tmp;
	tmp->plc = node;

	return tmp;
}

/*右旋转*/ 
Node* Right_Rotate(Node** root, Node* node)
{
	Node* tmp = node->plc;
	node->plc = tmp->prc;
	if (tmp->prc != NULL) {
		tmp->prc->pp = node;
	}

	tmp->pp = node->pp;

	if (parent(node) == NULL) {
		*root = tmp;
	}
	else {
		if (node->pp->plc = node) {
			node->pp->plc = tmp;
		}
		else {
			node->pp->prc = tmp;
		}
	}
	tmp->prc = node;
	node->pp = tmp;

	return tmp;
}

/*插入过程*/
RESULT _insert_(Node** root, Node* node)
{
	Node* cur = *root;
	Node* tmp = NULL;

	while (cur != NIL) 
	{
		tmp = cur;
		if ( node->val > cur->val ) {
			cur = cur->prc;
		}
		else if (node->val < tmp->val) {
			cur = cur->plc;
		}
		else {
			cur->cnt++;
			return 2;
		}
	}

	node->pp = tmp;
	if (node->key < tmp->key) {
		tmp->plc = node;
	}
	else {
		tmp->prc = node;
	}

	return SUCCESS;
}

/*插入节点后的调整函数*/
void insertadjust(Node**root,Node* node)
{
	while (parent(node)->color == RED)
	{
		/*这里分两种情况,叔结点是红色,叔结点不是红色(叔结点是NULL或者叔结点是黑色)*/
		/*叔结点是红色*/
		if (uncle(node) != NIL && uncle(node)->color == RED) {
			parent(node)->color = BLACK;
			uncle(node)->color = BLACK;
			grandparent(node)->color = RED;
			node = grandparent(node);
			if (parent(node) == NULL)
			{
				break;
			}
		}
		/*叔结点是空或者叔结点是黑色*/
		else {
			if (parent(node) == grandparent(node)->plc) {
				if (node == parent(node)->prc) {
					/*左旋*/
					node = Left_Rotate(root, parent(node))->plc;
				}
				parent(node)->color = BLACK;
				grandparent(node)->color = RED;
				/*右旋*/
				Right_Rotate(root, grandparent(node));
			}
			else {
				if (node == parent(node)->plc) {
					/*右旋*/
					node = Right_Rotate(root, parent(node))->prc;
				}
				parent(node)->color = BLACK;
				grandparent(node)->color = RED;
				/*左旋*/
				Left_Rotate(root, grandparent(node));
			}
			break;
		}
	}
}

/*插入节点,并进行必要的调整*/
RESULT insert(Node** root, int key, int val)
{
	Node* node = NULL;
	
	if (*root == NULL) {
		NIL = (Node*)malloc(sizeof(Node));
		*root = (Node*)malloc(sizeof(Node));
		NIL->color = BLACK;
		(*root)->color = BLACK;
		(*root)->plc = NIL;
		(*root)->prc = NIL;
		(*root)->pp = NULL;
		(*root)->key = key;
		(*root)->val = val;
		(*root)->cnt = 1;
		return SUCCESS;
	}
	else 
	{
		node = createnode(key, val);
		if (!node) {
			perror("malloc failure!!!\n");
			return FAILURE;
		}
	}
	/*插入节点*/
	if (2 == _insert_(root, node)) /*返回2说明插入的值已经存在,只需要计数变量+1即可*/ {
		return SUCCESS;
	}

	/*父节点如果是黑色,就不会冲突,不会违背任何性质,如果父节点是红色,则需要调整*/
	insertadjust(root, node);

	(*root)->color = BLACK;
	return SUCCESS;
}

Node* search(Node * root, int key)
{
	Node* node = root;
	while (node != NULL)
	{
		if (key > node->key) {
			node = node->prc;
		}
		else if (key < node->key) {
			node = node->plc;
		}
		else
			break;
	}
	return node;
}


void deleteadjust(Node** root, Node* node)
{
	/*nil节点的用处体现在此处,刚进入此函数是,node是nil节点,这样才不会报错,不然如果没有nil,而是NULL,
	此处肯定是要报错的(node->color),如果不使用nil节点而用null,要想跳过这个错误,只能在这个函数外
	先进行一次处理,使得node不在是NULL以后才能进入此函数*/
	while (node != *root && node->color == BLACK)
	{
		/*node节点经过迭代之后,就不能确定其实其父节点的左子节点还是右子节点了,因此分两种情况处理,
		两种情况下,旋转方向不同,因此其兄弟节点的左右子节点颜色判断要相反,case 4 和 case 5 体现了这一点*/
		if (node == parent(node)->plc) {
			Node* Parent = parent(node);
			Node* Brother = parent(node)->prc;/*默认取右子树的最左子节点,所以兄弟节点是右孩子*/

			if (Brother->color == RED) {
				/*case 1:兄弟节点brother红色,父节点P,brother的左右孩子(不为NIL,否则原来就不平衡)一定是黑,
				操作:Parent和Brother的颜色互换,在Parent点左旋转,现在P的左子树还是比右子树黑高少1,因此,
				将Brother更新一下,在node处重新进行判断调整*/
				Brother->color = BLACK;
				Parent->color = RED;
				Left_Rotate(root,parent(node));
				Brother = Parent->prc;
			}
			else if (Parent->color == BLACK && Brother->color == BLACK &&
				Brother->plc->color == BLACK && Brother->prc->color == BLACK) {
				/*case 2:Parent节点是黑色,Brother节点以及Brother节点的左右子节点都是黑色,
				操作:将Brother节点变成红色,在Parent节点处左旋,相当于原来以P节点(现在是以B节点)为根子节点
				的子树黑高整体减少1,用Brother节点代替原来的node节点,重新进行判断调整*/
				Brother->color = RED;
				Left_Rotate(root,Parent);
				node = Brother;
				continue;
			}
			else if (Parent->color == RED &&
				Brother->plc->color == BLACK && Brother->prc->color == BLACK) {
				/*case 3:Parent是红色,Brother的孩子们都是黑色
				操作:将Parent变为黑色,Brother变为红色即可满足性质*/
				Parent->color = BLACK;
				Brother->color = RED;
				node = *root;
			}
			else if (Brother->color == BLACK && Brother->prc->color == RED) {
				/*case 4:Parent为任意色,如果Brother的右子节点是红色,左子节点颜色任意
				Brother颜色编程parent的颜色,Parent的颜色变为黑色,Brother右子节点的颜色改为黑色*/
				Brother->color = Parent->color;
				Parent->color = BLACK;
				Brother->prc->color = BLACK;
				Left_Rotate(root,Parent);
				node = *root;
			}
			else if (Brother->color == BLACK &&
				Brother->plc->color == RED && Brother->prc->color == BLACK) {
				/*case 5:Parent颜色任意,Brother颜色是黑色,Brother的左子节点是红色,右孩子是黑色,
				操作:B的左子节点变为黑色,B变为红色,然后在B出右旋,处理以后就变成了case 4的情况*/
				Brother->plc->color = BLACK;
				Brother->color = RED;
				Right_Rotate(root,Brother);
				Brother = Brother->plc;/*Brother的左子节点代替了原来brother的位置,因此更新Brother的指向*/
				/*对于case 5 处理一下之后变成了case 4的情况,在case 4处理跳出*/
			}
		}
		else {
			Node* Parent = parent(node);
			Node* Brother = parent(node)->plc;/*默认取右子树的最左子节点,所以兄弟节点是右孩子*/

			if (Brother->color == RED) {
				/*case 1*/
				Brother->color = BLACK;
				Parent->color = RED;
				Right_Rotate(root, parent(node));
				Brother = Parent->plc;
			}
			else if (Parent->color == BLACK && Brother->color == BLACK &&
				Brother->plc->color == BLACK && Brother->prc->color == BLACK) {
				/*case 2*/
				Brother->color = RED;
				Right_Rotate(root, Parent);
				node = Brother;
				continue;
			}
			else if (Parent->color == RED &&
				Brother->plc->color == BLACK && Brother->prc->color == BLACK) {
				/*case 3*/
				Parent->color = BLACK;
				Brother->color = RED;
				node = *root;
			}
			else if (Brother->color == BLACK && Brother->plc->color == RED) {
				/*case 4*/
				Brother->color = Parent->color;
				Parent->color = BLACK;
				Brother->plc->color = BLACK;
				Right_Rotate(root, Parent);
				node = *root;
			}
			else if (Brother->color == BLACK &&
				Brother->prc->color == RED && Brother->plc->color == BLACK) {
				/*case 5*/
				Brother->prc->color = BLACK;
				Brother->color = RED;
				Left_Rotate(root, Brother);
				Brother = Brother->prc;
			}
		}
	}
	node->color = BLACK;
}

void _delete_(Node** root, Node* node)
{
	Node* tmp = NULL;/*实际要被删除的点*/

	if (node->plc == NIL || node->prc == NIL) {
		tmp = node;
	}
	else {
		Node* rightsubtree = node->prc;
		Node* minright = rightsubtree;

		while (rightsubtree->plc != NIL) {
			minright = rightsubtree->plc;
			rightsubtree = rightsubtree->plc;
		}
		tmp = minright;
	}
	Node* s = NULL;/*顶替实际要被删除的点的子节点*/
	if (tmp->plc != NIL) {
		s = tmp->plc;
	}
	else {
		s = tmp->prc;
	}

	s->pp = parent(tmp);
	if (tmp->pp == NIL) {
		*root = s;
	}
	else {
		if (tmp == parent(tmp)->plc) {
			parent(tmp)->plc = s;
		}
		else {
			parent(tmp)->prc = s;
		}
	}

	if (tmp != s) {
		node->key = tmp->key;
		node->val = tmp->val;
	}
	/*实际要删除的点的颜色,如果是红色,删除之后不影响,如果是黑色,则需要调整*/
	if (BLACK == tmp->color) {
		if (s->color == RED) { /*实际被删除的点的子节点如果是红色,直接将其变为黑色,即满足性质*/
			s->color = BLACK;
		}
		else {
			deleteadjust(root, s);
		}
	}
}

/*删除指定key的元素*/
RESULT delete(Node** root, int key)
{
	Node* node = search(*root, key);

	if (node == NIL)
	{
		return SUCCESS;
	}

	/*如果实际删除的点是个黑色的,就需要调整,不然违背红黑树性质*/
	_delete_(root,node);

	return SUCCESS;
}

测试代码:

/*test.c*/
#include "RB-Tree.h"

int main()
{
	Node* root = NULL;
	//int elemarry[] = { 16, 12, 18, 10, 14, 17, 20, 9, 11, 13, 15, 8 };
	int elemarry[] = { 1, 6, 11, 13, 15, 17, 22, 25,27 };

	for (int i = 0; i < sizeof(elemarry) / sizeof(int); i++)
	{
		if (SUCCESS != insert(&root, elemarry[i], elemarry[i]))
		{
			perror("insert failure!!!\n");
			return FAILURE;
		}
	}

	delete(&root,17);

	return 0;
}

以上代码写的比较随意,基本上保证了代码逻辑上没有什么问题,也阐述了红黑树的插入和删除操作,但是存在一些编码风格和小的BUG的风险,等到真正使用的时候再去仔细的修订吧。但是,若您看出了比较明显的BUG,还请指出,以免错误误导了更多人,谢谢!

 

好了,到此为止关于二叉搜索树的故事就告一段落了,一路走了,我们从BSTAVL,再到RB-Tree,操作越来越复杂,但是其性能也是越来越好,关于其他的一些变种,以后就不介绍了,掌握了这些,将来再去学习那些变种树也会得心应手了。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值