红黑树系列三:红黑树的删除

一、红黑树定义

 红黑树需要满足下面4个条件:

     1、每个节点不是红色就是黑色。
     2、根节点为黑色。
     3、如果节点为红色,其子节点必须为黑色。
     4、任意一个节点到到NULL(树尾端)的任何路径,所含之黑色节点数必须相同。

二、红黑树的删除

对二叉查找树,我们知道删除的结点可能有三种情况:

(1)为叶子结点,

(2)左子树或右子树有一个为空,

(3)左右子树都不空。假设删除节点为A。


对于(1):直接删除即可。

对于(2):删除的方法,A的父节点绕过A节点使其指向A左子树(右子树为空)、右子树(左子树为空时)。

对于(3):一般的删除策略:用A的左子树最大数据或右子树最小数据(假设B节点)代替A节点的数据,并删除B节点(B节点要么为叶子节点,要么右子树不为空,左子树为空)。


因此,仅从结构来看,二叉树上实质被删除的节点最多只可能有一个子树。其实红黑树的删除与二叉查找树的删除有点类似,只不过删除节点后可能会破坏红黑树的性质,我们需要做平衡处理,是树重新满足红黑树的4条性质。


由上面介绍知,删除需要找到“代替点”来代替删除点而被删除,代替点Y至少有一颗子树为NULL。

如果代替点Y的颜色为红色直接删除Y,不会破坏红黑树的性质。

如果代替点Y的颜色为黑色直接删除Y

(1)所有包含Y节点路径的黑高度会降低(可能会破坏条件4);

(2)如果Y有红色子节点,Y又有红色父节点,删除Y后,可能会出现红-红(破坏条件3);

(3)如果Y是根节点,Y有红色子节点,Y删除后,可能会破坏条件2(根节点必须为黑色)。


含有节点Y的路径的黑高度都减少了一。如果不改变含有Y路径的黑高度,则树的其他路径需要改变来适应它。想办法恢复原来含Y节点的路径的黑高度。做法就是:无条件的把Y节点的黑色,推到它的子节点X上去。(X可能是NIL节点)。这样,X就可能具有双重黑色,或同时具有红黑两色。

X具有红黑两色:直接把X涂成黑色,结束。

X具有双黑色:把这种情况向上推一直推到根节点(调整树结构和颜色,X的指向新的双黑色节点,X不断向上移动),让根节点具双黑色,这时,直接把X的一层黑色去掉就行了(因为根节点被包含在所有的路径上,所以这样做所有路径同时黑高减少一,不会破坏红黑特征)。假设X的父节点为P,X的兄弟节点为W(兄弟节点一定存在,因为X具有双黑色,原来树满足红黑性质),因此根据W的颜色分为4中情况:(下面针对X是左儿子的情况讨论,右儿子对称)

case1:W为红色

《红黑树系列三:红黑树的删除》

     由于W是红色的,因此其儿子节点和父节点必为黑色,只要将W和其父节点的颜色对换,在对父节点进行一次左旋转,便将W的左子节点放到了X的兄弟节点上,X的兄弟节点变成了黑色,且红黑性质不变。但还不算完,只是暂时将情况1转变成了下面的情况2或3或4。


伪算法:(w与P颜色对换,对P进行左旋)

w->color=BLACK;

x->Parent->color=RED;

LeftRotate(x->parent)


case2:W的黑色,并且左右孩子都为黑色。

《红黑树系列三:红黑树的删除》

可以将X的一重黑色和W的黑色同时去掉,而转加给他们的父节点上,这是X就指向它的父节点了,因此此时父节点具有双重颜色了。这一重黑色节点上移。

伪算法:

w->color=RED;

x=x->Parent;  //向上递归

case3:W的黑色,并且左孩子为红色,右孩子为黑色。

《红黑树系列三:红黑树的删除》

通过交换W和其左子节点的颜色并进行一次向右旋转就可转换成下面的第四种情况。注意,原来L是红色的,所以L的子节点一定是黑色的,所以旋转中L节点的一个子树挂到之后着为红色的W节点上不会破坏红黑性质。变形后黑色高度不变。

伪算法:(交换L、W颜色,对W右旋)

w->color=RED;

w->Left=BLACK;

LeftRotate(w);

case4:W的黑色,并且左孩子为黑色,右孩子为红色。

《红黑树系列三:红黑树的删除》

做一次左旋,W就处于根的位置,将W保持为原来的根的位置的颜色,同时将W的两个新的儿子节点的颜色变为黑色,去掉X的一重黑色。这样整个问题也就得到了解决。递归结束。(在代码上,为了标识递归结束,我们把X指向根节点)

伪算法:

w->color=x->parent->color

W->Right->color=BLACK;

x->Parent=BLACK;

LeftRotate(x->parent);

  因此,只要按上面四种情况一直递归处理下去,X最终总会指向根结点或一个红色结点,这时我们就可以结束递归并把问题解决了。

三、源代码

红黑节点结构:

	//定义红黑树的数据结构
	typedef enum colorType{RED,BLACK} colorType;
typedef int elementType;
typedef struct RedBlackNode
{
	elementType element;
	struct RedBlackNode *Left;
	struct RedBlackNode *Right;
	struct RedBlackNode *Parent;
	colorType color;  //颜色标记
}RBTree,*PRBTree;

黑子节点的定义:

// 红黑树的NIL节点    
static struct RedBlackNode NIL = {0, 0, 0, 0, BLACK};   
  
#define PNIL (&NIL)   // NIL节点地址  

1.删除操作

删除操作分两步,先按二分查找树的删除方法,找到删除节点并删除,然后再对红黑树的做平衡处理。

(1)找到删除节点和删除节点的子节点

(2)改变节点之间的亲子关系

(3)平衡处理

PRBTree RBT_Delete(elementType data,PRBTree root)
{
	PRBTree relDeleteNode;//实际删除的节点
	PRBTree	deleteNodeChild; //实际删除节点的子节点
	PRBTree node=RBT_Find(data,root);
	if(node==PNIL) //没找到要删除的节点
	{
		return root;//直接返回根节点
	}
	//程序执行到此,node指向要删除的节点
	if(node->Left==PNIL || node->Right==PNIL)  //左右子树有一个为空
		relDeleteNode=node; 
	else  //查找node右子树最小节点
	{
		PRBTree nodeTmp=node->Right;
		while(nodeTmp->Left!=PNIL)
		{
			nodeTmp=nodeTmp->Left;
		}
		//node指向右子树最小节点
		
		relDeleteNode=nodeTmp;
	}
	//上面程序完成的功能:使relDeleteNode指向要删除的节点

	if(relDeleteNode->Right!=PNIL)
		deleteNodeChild=relDeleteNode->Right;
	else if(relDeleteNode->Left!=PNIL)
		deleteNodeChild=relDeleteNode->Left;
	else
		deleteNodeChild=PNIL;

	deleteNodeChild->Parent=relDeleteNode->Parent;
	if(relDeleteNode->Parent==PNIL) //实际删除的节点为根节点
		root=deleteNodeChild; 

	else if(relDeleteNode==relDeleteNode->Parent->Left)
	{
		relDeleteNode->Parent->Left=deleteNodeChild;
	}
	else if(relDeleteNode==relDeleteNode->Parent->Right)
	{
		relDeleteNode->Parent->Right=deleteNodeChild;
	}
	if(relDeleteNode!=node)  //左子树的值
	{
		node->element=relDeleteNode->element;
		
	}
	if(relDeleteNode->color==BLACK)  //删除红色不影响所在路径的黑高度,因此只需要对删除黑节点进行处理
	{
		//翻转处理
		Delete_Rebalance(deleteNodeChild,root);
	}
	return root;
}

查找函数的代码如下:

PRBTree RBT_Find(elementType data,PRBTree root)
{
	PRBTree node=root;
	while(node!=PNIL)
	{
		if(data<node->element)
		{
			node=node->Left;
		}
		else if(node->element<data)
		{
			node=node->Right;
		}
		else
		{
			return node;  //查找到指定节点
		}
	} //while结束
	return PNIL;
}

2.删除平衡处理

按照上面讨论的4种情况,删除平衡处理函数如下:

PRBTree Delete_Rebalance(PRBTree x,PRBTree root)
{
	while(x!=root&&(x->color==BLACK))  //出现双重黑色
	{
		if(x==x->Parent->Left) //左子树
		{
			PRBTree y=x->Parent->Right;  //y为x的兄弟
			if(y->color==RED)         //兄弟的颜色为红,情况1
			{
				x->Parent->color=RED;
				y->color=BLACK;
				root=LeftRotate(x->Parent,root);
			}
			else                       //兄弟的颜色为黑
			{
				if(y->Left->color==BLACK&&y->Right->color==BLACK)  //两个兄弟都为黑色,情况2
				{
					y->color=RED;
					x=x->Parent;
				}
				else
				{
					if(y->Right->color==BLACK)  //右子节点为黑色,情况3
					{
						y->color=RED;
						y->Left->color=BLACK;
						root=RightRotate(y,root);
						y=x->Parent->Right;
					}
					y->color=x->Parent->color;
					x->Parent->color=BLACK;
					y->Right->color =BLACK;
					root=LeftRotate(x->Parent,root);
					x=root;  //结束while循环
					break;
				}
			}
		}
		else                 //右子树
		{
			PRBTree y=x->Parent->Left;  //y为x的兄弟
			if(y->color==RED)         //兄弟的颜色为红,情况1
			{
				x->Parent->color=RED;
				y->color=BLACK;
				root=RightRotate(x->Parent,root);
			}
			else                       //兄弟的颜色为黑
			{
				if(y->Left->color==BLACK&&y->Right->color==BLACK)  //两个兄弟都为黑色,情况2
				{
					y->color=RED;
					x=x->Parent;
				}
				else
				{
					if(y->Left->color==BLACK)  //右子节点为黑色,情况3
					{
						y->color=RED;
						y->Right->color=BLACK;
						root=LeftRotate(y,root);
						y=x->Parent->Left;
					}
					y->color=x->Parent->color;
					x->Parent->color=BLACK;
					y->Left->color =BLACK;
					root=RightRotate(x->Parent,root);
					x=root;  //结束while循环
					break;
				}
			}
		}
	}//while循环结束
	x->color=BLACK;   //如果x节点颜色原来为红色,直接改为黑色
	return root;
}

源代码下载地址:
http://download.csdn.net/detail/lpp0900320123/8027383

    原文作者:算法小白
    原文地址: https://blog.csdn.net/lpp0900320123/article/details/39647527
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞