红黑树的插入与删除(二)

上文回溯

上一篇插入节点分析中已经提过,红黑树的插入节点相对于AVL树来讲,需要的调节平衡的操作比较少,一方面表示红黑树的平衡定义与AVL树不同,另一方面也说明了红黑树不是严格意义上的平衡树,即查询操作不是严格的O(log n)。不过带来的好处则是需要的调节操作少,在插入、删除节点时具有较少的性能消耗,也就是所谓的统计性能较好。

回顾一下红黑树的五个特性:

1.节点颜色为红或者为黑

2.根节点为黑

3.叶子节点为黑(叶子作为结束标志,可以以空表示或者自定义实体节点作为结束标志,后面树中没有画出来)

4.红色节点必须有两个黑色子节点(子节点可以为结束标志)

5.任一节点出发到叶节点路径上包含相同数量的黑色节点

因为删除操作很多依赖于特性5,所以需要记熟。

删除节点

AVL树中的删除过程可知,给出的待删除的节点,在执行时可能实际删除的只是它的“替身”,而对原来要删除的节点只是更改了它的值而已。这里所说的过程也就是,当待删除节点的左右子节点都存在(不是表示结束的叶子节点),则找到该节点的左子树最大值节点或者右子树的最小值节点,将该极值节点的值赋值给待删除节点,然后删除该极值节点。

当待删除节点没有同时包含左子节点和右子节点,则当前节点即为极值节点(以该节点为根的子树中,该节点即为此子树范围内的极值)。也就是说,当该节点无子节点或者只有一个子节点时,该节点即为极值节点。

下面讨论的待删除节点都是待删除的极值节点。待删除节点已经确定为某个极值点后,则开始进行平衡检查工作,平衡检查的起始节点即为该待删除节点,如果不能一次检查通过,则更换检查节点,迭代进行平衡检查,检查通过后,删除待删除节点(不是当前的检查节点)。

代码演示:

选择极值点:

void delete(node root,int value){//根据值来删除节点
		while(root.value!=value){//找到删除位置
			if(value<root.value){
				root=root.left;
			}else{
				root=root.right;
			}
		}
		int temp=find_the_point(root).value;
		root.value=temp;
		root=find_the_point(root);//是否要给待删除的节点找替身
		index=root;//指定要删除的节点
		delete_check(root);//删除检查
		delete_dir(index);//检查完成,删除节点
	}
	node find_the_point(node t){//是否要给待删除的节点找替身
		if(children_number(t)==2){//两个孩子都有,则找替身,并返回替身
			return min_right(t.right);//返回左子树的最大值
		}
		return t;
	}
	int children_number(node t){//返回子节点个数
		int c=0;
		if(t.left!=null){
			c++;
		}
		if(t.right!=null){
			c++;
		}
		return c;
	}
	node min_right(node t){//返回右子树的最小值
		while(t.left!=null){
			t=t.left;
		}
		return t;
	}

情景演示

类似与节点插入的演示,在删除节点之前,先描述一个删除场景


x:待删除节点,f:父节点,s:兄弟节点,sr:兄弟节点右孩子

通过以f节点为根,进行左旋转,将f节点的颜色赋予s节点,f节点变为黑。能够实现的是:右边一条线黑色节点数不变,左边黑色节点树加一,从而可以实现删除节点x,不会导致特性5不符。

情景分析

情形0:当前检查节点为红色节点。

由待删除节点为极值节点,最多一个子节点,由特性4,5可知,如果待删除节点为红色,则该节点无子节点,删除不影响树的平衡,此时检查平衡完成。

情形1:当前检查节点为黑色节点,只有一个子节点存在时

当前检查节点最多只有一个子节点,由特性5可知,该子节点为红


此时,无论x的子节点是左子节点或者右子节点,只需要将子节点的值赋予自身即可,更改待删除节点为子节点,完成检查。

代码演示:

删除红色子节点:

void delete_and_take(node t){//以子节点变色代替,其实就是用了子节点的值,还删了子节点
		if(t.left!=null){
			t.value=t.left.value;
			index=t.left;
			return;
		}
		if(t.right!=null){
			t.value=t.right.value;
			index=t.right;
			return;
		}
		if(t.parent==null){
			root=t;
		}
	}

情形2:当前检查节点为黑色节点,父节点存在,兄弟节点为黑

当前检查节点为黑色节点时,因为删除会导致特性5不符,需要进行平衡调节。删除根据兄弟节点的情形如下

情形2.1:当兄弟节点存在红色子节点时

通过旋转变色操作保持以父节点为根的子树部分,保持整体平衡性,完成检查,具体场景如下

情形2.1.1:当兄弟节点右孩子为红色节点时,如上面情景演示,通过变色左旋即可


情形2.1.2:当兄弟节点左孩子为红色节点时,先进行变色右旋,再进行左旋


代码演示:

兄弟节点存在红孩子:

void left_or_right(node s){//具有至少一个红色节点,此时的s是待删除节点的兄弟节点
		if(check_for_side(s)){//s是父节点的左节点
			if(s.left!=null){
				s.left.color=false;
				s.color=s.parent.color;
				s.parent.color=false;;
				right_rotate(s.parent);
				return;
			}
			if(s.right!=null){//根据特性5,s有子节点的话,子节点为红
				s.right.color=s.parent.color;
				s.parent.color=false;				
				s=left_rotate(s);
				right_rotate(s.parent);
				return;
			}
			
		}else{//s是父节点的右节点
			if(s.right!=null){//根据特性5,s有子节点的话,子节点为红
				s.right.color=false;//右孩子变为黑色
				s.color=s.parent.color;//将父节点的颜色换给s
				s.parent.color=false;//父节点颜色变为黑色
				
				left_rotate(s.parent);
				return;
			}
			if(s.left!=null){
				s.left.color=s.parent.color;
				s.parent.color=false;
				s=right_rotate(s);
				left_rotate(s.parent);
				return;
			}
		}
	}

情形2.2:当兄弟节点不存在红色子节点时

该情形下采用的一种方式,类似于“上诉”的场景,将父节点作为新的检查节点,迭代进行平衡检查。根据父节点的颜色,进行颜色变换,当前步骤不需要进行旋转。有以下两种情形:

情形2.2.1:父节点为红


当父节点为红时,将兄弟节点变为红,父节点变为黑,则f-s路线上黑色节点个数不变,f-x待删除路线上黑色节点个数加一,删除节点x不会导致特性5不符,保持整体平衡性,完成检查。

情形2.2.2:父节点为黑


通过将兄弟节点变为红,维持以f为根的子树内部平衡性,即局部平衡,此时f为新的待检查节点,进行迭代平衡检查。

情形3:当前检查节点为黑色节点,父节点存在,兄弟节点为红


当兄弟节点s为红,对f,s进行变换颜色,以f节点为根作左旋转。以上图中的左右侧来说,旋转前f右侧的子树,与旋转后s右侧的子树,黑色节点数不变,不存在特性5不符。但是检查点x换做新的检查点x(虽然指向的节点不变,但是环境已经改变)。

所以此次的调节操作目的在于,对同样的检查点提供新的检查环境(以当前为例,旋转之后,如果sl节点存在红孩子,则根据情形2.1,最多再两次旋转即可解决问题;如果sl节点没有红孩子,则根据情形2.2.1,一次旋转都不用,只需要变个色即可解决问题)。

代码演示:

兄弟节点为红:

/*
		兄弟节点为红,则父节点黑,兄弟节点的子节点为黑
	*/
	void delete_s_red(node t){//兄弟节点为红
		node s=brother(t);
		s.color=false;//兄弟节点变为黑色
		s.parent.color=true;//父节点变为红色
		if(check_for_side(t)){//如果t是左节点
			t=left_rotate(s.parent);//左旋
			delete_check(t.left.left);
		}else{//右节点,则右旋
			t=right_rotate(s.parent);
			delete_check(t.right.right);
		}		
	}

情形4:当前检查节点为黑色节点,父节点不存在,两个子节点都存在时

当前检查节点为根节点,并且两个子节点都存在,则很明显由下层迭代检查到根节点,此时不作处理,检查平衡完成(例如由情形2.2.2的“上诉”,如果上诉到根节点,则表示以根节点作为的根节点的子树内部保持局部平衡性,也就是说整个树都是平衡的)。

删除节点时进行判断,选择待执行的调节操作。

代码演示:

插入节点检查:

void delete_check(node t){//删除检查
		if(t.parent!=null){//不是根节点
			if(t.color){//为红色节点,且无子节点
				return;
			}
			if((t.left==null&&t.right!=null)||(t.left!=null)&&t.right==null){//只有一个子节点
				delete_and_take(t);
				return;
			}
			node s=brother(t);//获取t的兄弟节点
			if(s.color){//兄弟节点为红
				delete_s_red(t);//旋转
			}else{//兄弟节点为黑
				if((s.left!=null&&s.left.color)||(s.right!=null&&s.right.color)){//兄弟有红孩子
					left_or_right(s);
				}else{//没有孩子或者有黑孩子
					s.color=true;//变为红
					if(s.parent.color){//如果父节点为红,置为黑,结束
						s.parent.color=false;
					}else{
						if(s.parent.parent!=null){//父节点不为根节点
							delete_check(s.parent);
						}
					}
				}
			}
		}else{//根节点
			if(root.left!=null){
				root.value=root.left.value;
				root.left=null;
				return;
			}
			if(root.right!=null){
				root.value=root.right.value;
				root.right=null;
				return;
			}
			root=null;
		}
		
	}

在该代码中做了一点优化,在情形2.2.2中的“上诉”中,直接判断是否是根节点,如果是根节点则检查完成,而没有以新的检查点再次执行平衡检查操作。

总结:

由以上的操作可知,红黑树的平衡相对于AVL树来说不是特别严禁的,但是带来的好处则是调节平衡操作的简洁性。插入节点最多需要进行两次旋转即可达到平衡,删除节点最多需要三次旋转即可平衡。

示例:

以12 ,1 ,9 ,2, 0, 11, 7, 19, 4 ,15, 18 ,5 ,14, 13 ,10, 16 ,6, 3, 8, 17作为插入节点和删除节点测试

插入删除完整代码如下:

public class t{
	public static void main(String[] args){
		int[] arr=new int[]{12 ,1 ,9 ,2, 0, 11, 7, 19, 4 ,15, 18 ,5 ,14, 13 ,10, 16 ,6, 3, 8, 17};
		tree tr=new tree();
		for(int i:arr){
			tr.insert(i);
		}
		for(int i=0;i<16;i++){
			tr.delete(arr[i]);
		}
		System.out.println("==========================");
		tree.pre_show(tr);
    }
}
final class node{//简单起见,所有设置属性的方法省略,提供直接访问
	boolean color=true;//构造默认颜色红色,true表示红色,false表示黑色
	int value=0;//键值
	node left=null;//左孩子
	node right=null;//右孩子
	node parent=null;//父节点

	node(int value){
		this.value=value;
	}
}

final class tree{
	static node index=null;
	node root=null;
	tree(){}
	void insert(int value){
		if(root==null){//这里的root表示根节点
			root=new node(value);
			root.color=false;//设置根节点的颜色为黑色
		}else{
			insert(root,value);
		}
	}
	void insert(node root,int value){//插入节点
		node temp=null;//记录父节点
		boolean left=false;//记录新节点在父节点tmep的左右哪边
		while(root!=null){//找到插入位置
			while(root!=null&&value<root.value){//左边找
				temp=root;
				root=root.left;
				left=true;
			}
			while(root!=null&&value>root.value){//右边找
				temp=root;
				root=root.right;
				left=false;
			}
			if(root!=null&&root.value==value){
				System.out.println("include the same value!");
				break;
			}
		}
		node t=new node(value);//创建该节点
		if(left){//设置父节点一边的孩子
			temp.left=t;
		}else{
			temp.right=t;
		}
		t.parent=temp;//设置新节点的父节点
		insert_check(t);//插入检查
	}
	void delete(int value){
		delete(root,value);
	}
	void delete(node root,int value){//根据值来删除节点
		while(root.value!=value){//找到删除位置
			if(value<root.value){
				root=root.left;
			}else{
				root=root.right;
			}
		}
		int temp=find_the_point(root).value;
		root.value=temp;
		root=find_the_point(root);//是否要给待删除的节点找替身
		index=root;//指定要删除的节点
		delete_check(root);//删除检查
		delete_dir(index);//检查完成,删除节点
	}
	node find_the_point(node t){//是否要给待删除的节点找替身
		if(children_number(t)==2){//两个孩子都有,则找替身,并返回替身
			return min_right(t.right);//返回左子树的最大值
		}
		return t;
	}
	int children_number(node t){//返回子节点个数
		int c=0;
		if(t.left!=null){
			c++;
		}
		if(t.right!=null){
			c++;
		}
		return c;
	}
	node min_right(node t){//返回左子树的最大值
		while(t.left!=null){
			t=t.left;
		}
		return t;
	}
	void delete_check(node t){//删除检查
		if(t.parent!=null){//不是根节点
			if(t.color){//为红色节点,且无子节点
				return;
			}
			if((t.left==null&&t.right!=null)||(t.left!=null)&&t.right==null){//只有一个子节点
				delete_and_take(t);
				return;
			}
			node s=brother(t);//获取t的兄弟节点
			if(s.color){//兄弟节点为红
				delete_s_red(t);//旋转
			}else{//兄弟节点为黑
				if((s.left!=null&&s.left.color)||(s.right!=null&&s.right.color)){//兄弟有红孩子
					left_or_right(s);
				}else{//没有孩子或者有黑孩子
					s.color=true;//变为红
					if(s.parent.color){//如果父节点为红,置为黑,结束
						s.parent.color=false;
					}else{
						if(s.parent.parent!=null){//父节点不为根节点
							delete_check(s.parent);
						}
					}
				}
			}
		}else{//根节点
			if(root.left!=null){
				root.value=root.left.value;
				root.left=null;
				return;
			}
			if(root.right!=null){
				root.value=root.right.value;
				root.right=null;
				return;
			}
			root=null;
		}
		
	}
	void delete_and_take(node t){//以子节点变色代替,其实就是用了子节点的值,还删了子节点
		if(t.left!=null){
			t.value=t.left.value;
			index=t.left;
			return;
		}
		if(t.right!=null){
			t.value=t.right.value;
			index=t.right;
			return;
		}
		if(t.parent==null){
			root=t;
		}
	}
	/*
		兄弟节点为红,则父节点黑,兄弟节点的子节点为黑
	*/
	void delete_s_red(node t){//兄弟节点为红
		node s=brother(t);
		s.color=false;//兄弟节点变为黑色
		s.parent.color=true;//父节点变为红色
		if(check_for_side(t)){//如果t是左节点
			t=left_rotate(s.parent);//左旋
			delete_check(t.left.left);
		}else{//右节点,则右旋
			t=right_rotate(s.parent);
			delete_check(t.right.right);
		}		
	}
	void left_or_right(node s){//具有至少一个红色节点,此时的s是待删除节点的兄弟节点
		if(check_for_side(s)){//s是父节点的左节点
			if(s.left!=null){
				s.left.color=false;
				s.color=s.parent.color;
				s.parent.color=false;;
				right_rotate(s.parent);
				return;
			}
			if(s.right!=null){//根据特性5,s有子节点的话,子节点为红
				s.right.color=s.parent.color;
				s.parent.color=false;				
				s=left_rotate(s);
				right_rotate(s.parent);
				return;
			}
			
		}else{//s是父节点的右节点
			if(s.right!=null){//根据特性5,s有子节点的话,子节点为红
				s.right.color=false;//右孩子变为黑色
				s.color=s.parent.color;//将父节点的颜色换给s
				s.parent.color=false;//父节点颜色变为黑色
				
				left_rotate(s.parent);
				return;
			}
			if(s.left!=null){
				s.left.color=s.parent.color;
				s.parent.color=false;
				s=right_rotate(s);
				left_rotate(s.parent);
				return;
			}
		}
	}
	
	void delete_dir(node t){//当前节点为红,则直接删除	
		if(t.parent==null){//根节点的话
			root=null;
			t=null;
			return;
		}
		if(t==t.parent.left){
			t.parent.left=null;
		}else{
			t.parent.right=null;
		}
	}
	void insert_check(node t){//插入检查
		if(t.parent==null){//如果问题已经上升到根节点,则就是check的最后一步
			t.color=false;//下层都已经整理好,直接改根节点为黑色
			root=t;
		}else{
			if(t.parent.color){//父节点存在,且为红
				if(uncle(t)!=null&&uncle(t).color){//如果叔节点存在且为红
					check_case1(t);//叔节点为红
				}else{
					check_case2(t);//叔节点为黑
				}
			}
		}
	}
	/*
		check_case1解决问题,父节点红色,则祖父节点为黑色,叔节点为红色的情况
		
	*/
	void check_case1(node t){//叔节点为红
		node uncle=uncle(t);
		t.parent.color=false;//父节点变黑
		uncle.color=false;//叔节点变黑
		uncle.parent.color=true;//祖父节点变红
		insert_check(uncle.parent);//以祖父节点作为新的检查点
	}
	/*
		check_case2解决问题,父节点红色,则祖父节点为黑色,叔节点不为红色的情况
		直接插入新节点不会出现这种情况,因为祖父节点到叔节点和到父节点情况,违背红黑树特性5
		所以这种情况是由check_case1变化颜色导致的
	*/
	void check_case2(node t){//叔节点为黑
		if(t==t.parent.right&&t.parent==grandparent(t).left){//左右拐弯
			t=left_rotate(t.parent);//以父节点为根左旋,将新的父节点返回
			t.color=false;
			t.parent.color=true;
			t=right_rotate(t.parent);//以父节点为根右旋,将新的父节点返回
		}else if(t==t.parent.left&&t.parent==grandparent(t).right){//右左拐弯
			t=right_rotate(t.parent);//以父节点为根右旋,将新的父节点返回
			t.color=false;
			t.parent.color=true;
			t=left_rotate(t.parent);//以父节点为根左旋,将新的父节点返回
		}else if(t==t.parent.left&&t.parent==grandparent(t).left){//左边一条道
			t.parent.color=false;
			t.parent.parent.color=true;
			t=right_rotate(grandparent(t));//以祖父节点为根右旋
		}else if(t==t.parent.right&&t.parent==grandparent(t).right){
			t.parent.color=false;
			t.parent.parent.color=true;
			t=left_rotate(grandparent(t));//以祖父节点为根左旋
		}
		
	}
	/*
		当父节点存在时,判断当前节点是父节点的左子节点或者右子节点
		返回true表示左,false表示右
	*/
	boolean check_for_side(node t){
		return t==t.parent.left?true:false;
	}
	node left_rotate(node t){//以t为根节点,左旋
		node par=t.parent;
		node t_rig=t.right;
		boolean l=false;
		if(par!=null){
			l=check_for_side(t);
		}
		t.right=t_rig.left;
		if(t_rig.left!=null){
			t_rig.left.parent=t;
		}
		t_rig.left=t;
		t.parent=t_rig;
		t_rig.parent=par;
		if(par!=null){
			if(l){
				par.left=t_rig;
			}else{
				par.right=t_rig;
			}
		}
		if(t_rig.parent==null){
			root=t_rig;
		}
		return t_rig;
	}
	node right_rotate(node t){//以t为根节点,右旋
		node par=t.parent;
		node t_lef=t.left;
		boolean l=false;
		if(par!=null){
			l=check_for_side(t);
		}
		t.left=t_lef.right;
		if(t_lef.right!=null){
			t_lef.right.parent=t;
		}
		t_lef.right=t;
		t.parent=t_lef;
		t_lef.parent=par;
		if(par!=null){
			if(l){
				par.left=t_lef;
			}else{
				par.right=t_lef;
			}
		}
		if(t_lef.parent==null){
			root=t_lef;
		}
		return t_lef;
	}
	node grandparent(node t){//获取t的祖父节点
		return t.parent.parent;
	}
	node brother(node t){//返回兄弟节点
		return t==t.parent.left?t.parent.right:t.parent.left;
	}
	/*
		获取t的叔节点,这里没有判断祖父节点是否存在的情况
	*/
	node uncle(node t){//
		node g=grandparent(t);
		return t.parent==g.left?g.right:g.left;
	}
	static void pre_show(tree tr){//前序遍历树
		pre_show(tr.root);
	}
	static void pre_show(node root){//前序遍历树
		if(root!=null){
			System.out.println("value:"+root.value+",color:"+(root.color==true?"-----------------":"black"));
			pre_show(root.left);
			pre_show(root.right);
		}
	}
	
}

参考:https://zh.wikipedia.org/wiki/%E7%BA%A2%E9%BB%91%E6%A0%91



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值