红黑树Java基本操作-201805

红黑树Java基本操作-201805

(只讨论怎么操作)[参考 java.util.TreeMap]

一、定义

    普通的二叉搜索树在插入或删除的时候,可能会出现树结构向一侧倾倒的情况。这时,这棵二叉树上将近似于链表。

    红黑树(RBTree)是一棵二叉搜索树,在每个节点位增加了一个表示颜色的存储位(RED/BLACK)。通过对从根到叶子的简单路径上节点颜色的约束,确保没有一条路径会比其他路径长出2倍,因而是近似平衡的。红黑树可以保证在最坏情况下基本动态集合操作的时间复杂度为O(lgn)。

    树的节点包含5个属性:key,left,right,parent,color。

    红黑树满足下列5条性质:

    1.每个节点非红即黑。

    2.根节点是黑色的。

    3.每个叶节点(null)是黑色的。

    4.若一个节点是红色的,则其两个子节点为黑色。

    5.对每个节点,从该节点到其后代各叶节点的简单路径上的黑色节点的个数(黑高)相同。

扫描二维码关注公众号,回复: 17187138 查看本文章

    引理:一棵有n个内部节点的红黑树高度至多为2lg(n+1)


class Nord{    //定义节点构造
	Nord parent;
	Nord left;
	Nord right;
	int element;
	boolean red;

	Nord(Nord parent, Nord left, Nord right, int element,boolean red) {
		super();
		this.parent = parent;
		this.left = left;
		this.right = right;
		this.element = element;
		this.red=red;
	}

	@Override
	public String toString() {
		return "Nord  [element=" + element +",red="+red+ "]";
	}	
	
}

二、旋转

    当对红黑树进行插入或删除操作时,可能导致该树违反其基本性质。为了维护这些性质,必须改变某些结构的颜色和指针。

    指针的修改是通过旋转来完成的。

    左旋

左旋动画

	//左旋-x.left和y.right不变,x.right=y.left,再用y替代x的位置,y.left=x
	public Nord leftRotate(Nord x) {
		Nord y;
		if(x!=null&&((y=x.right)!=null)) {
			x.right=y.left;
			if(y.left!=null) {
				y.left.parent=x;
			}
			y.parent=x.parent;
			if(x.parent==null) {
				root=y;
			}else if(x.parent.left==x) {
				x.parent.left=y;
			}else {
				x.parent.right=y;
			}
			y.left=x;
			x.parent=y;
			return y;
		}
		return null;
	}

    右旋-与左旋正相反

右旋动态示意图

//右旋
	public Nord rightRotate(Nord x) {
		Nord y;
		if(x!=null&&(y=x.left)!=null) {
			x.left=y.right;
			if(y.right!=null) {
				y.right.parent=x;
			}
			y.parent=x.parent;
			if(x.parent==null) {
				root=y;
			}else if(x.parent.left==x) {
				x.parent.left=y;
			}else {
				x.parent.right=y;
			}
			y.right=x;
			x.parent=y;
			return y;
		}
		return null;
	}
三、插入

    和二叉搜索树的insert操作基本一致,最后把插入的节点颜色设Red。为保持红黑树的性质,调用insertFixup重新着色并旋转。

	//插入
	public boolean insert(int z) {
		return insert(new Nord(null,null,null,z,RED),root);
	}
	
	//插入-和二叉搜索树的insert操作基本一致,最后把插入的节点颜色设Red,调用insertFixup重新着色并旋转
	public boolean insert(Nord x,Nord root) {
		if(x==null) {
			return false;
		}
		Nord y = null;
		while(root!=null) {
			y=root;
			if(x.element>root.element) {
				root=root.right;
			}else if(x.element<root.element) {
				root=root.left;
			}else return false;
		}
		x.parent=y;
		if(y==null) {
			this.root=x;
		}else if(x.element>y.element) {
			y.right=x;
		}else y.left=x;
		x.color=RED;
		insertFixup(x);
		return true;
	}

辅助函数-(过滤null值,防止空指针)

	//过滤null,避免空指针
	private Nord parentOf(Nord x) {
		return x==null?null:x.parent;
	}
	
	private Nord leftOf(Nord x) {
		return x==null?null:x.left;
	}
	
	private Nord rightOf(Nord x) {
		return x==null?null:x.right;
	}
	
	private void setColor(Nord x,boolean color) {
		if(x!=null) x.color=color;
	}

insertFixup函数

case 1:叔节点为红色

case 2.a:叔节点为黑色,当前节点为右孩子

这里写图片描述

case 2.b:叔节点为黑色,当前节点为左孩子

这里写图片描述

	/**	
	 * 	insert之后的重新着色和旋转	
	 * 
	 * 	y:当前x节点的叔节点
	 * 	当x的父节点为red时 循环:
	 * 	若x.parent是左孩子,
	 * 	case 1:当x的叔节点y为red时,重新染色,把x.parent和y都染成black,y.parent染成red,x指针上移,重赋为x.parent.parent;
	 * 	case 2:当x的叔节点y为black时,a.若x为右孩子,则令x=x.parent,在对当前的x左旋;再将当前x.parent染红,x.parent.parent染黑,对x.p.p右旋
	 *                                    b.若x为左孩子,则将当前x.parent染红,x.parent.parent染黑,对x.p.p右旋.
	 * 	若x.parent是右孩子,与左孩子正相反
	 * 
	 * 	@param x 插入的节点x
	 * */
	private void insertFixup(Nord x) {
		x.color=RED;
		while(x!=null && x!=root && x.parent.color==RED) {
			Nord y;
			if(x.parent==leftOf(parentOf(x.parent))) {        //x.parent为左孩子的情况
				y=rightOf(parentOf(x.parent));
				if(y!=null && y.color==RED) {                //case 1
					x.parent.color=BLACK;
					y.color=BLACK;
					setColor(parentOf(x.parent),RED);
					x=parentOf(x.parent);                //case 1 结束
				}else  {
					if(x.parent.right==x) {                //case 2.a
						x=x.parent;
						leftRotate(x);
					}
					x.parent.color=RED;                                        //case 2.b 
					setColor(parentOf(x.parent),BLACK);
					rightRotate(parentOf(x.parent));        //case 2.a 结束    //case 2.b 结束
				}
			}else {
				y=leftOf(parentOf(x.parent));                    //x.parent为右孩子的情况,与上面相反
				if(y!=null && y.color==RED) {
					x.parent.color=BLACK;
					y.color=BLACK;
					setColor(parentOf(x.parent),RED);
					x=parentOf(x.parent);
				}else {
					if(x.parent.left==x) {
						x=x.parent;
						rightRotate(x);
					}
					x.parent.color=RED;
					setColor(parentOf(x.parent),BLACK);
					leftRotate(parentOf(x.parent));
				}
			}
		}
		root.color=BLACK;                //root节点染黑
	}

四、删除

    先写几个辅助函数:

    transplant(Nord x,Nord y)-用节点y移植到x的位置

	//transplant:与二叉搜索树的transplant一致
	public void transplant(Nord x,Nord y){
		if(x!=null){
			if(x.parent==null){
				this.root=y;
			}else if(x.parent.left==x){
				x.parent.left=y;
			}else x.parent.right=y;
			if(y!=null){
				y.parent=x.parent;
			}
		}
	}
         public Nord min(Nord root)-返回该root下的最小节点
	//最小节点
	public Nord min(Nord root){
		Nord y = null;
		while(root!=null){
			y=root;
			root=root.left;
		}
		return y;
	}

    public Nord get(Nord root,int x)-取得元素为x的节点

	//取得节点x
	public Nord get(Nord root,int x) {
		if (root == null || x == root.element) {
			return root;
		}
		if (root.element > x) {
			return get(root.left, x);
		} else {
			return get(root.right, x);
		}
	}

     删除-remove(Nord x)

	//删除
	public boolean remove(int x) {
		return remove(get(root,x))==null?false:true;
	}
	
	/**
	 * 	这里的删除算法与二叉搜索树的remove方法基本思路一致,最后当originColor=BLACK时,调用deleteFixup(z)重新旋转着色。
	 * 	不同点:	1.节点y:a.当Nord x不存在两个子节点时,y即是x;
	 * 			b.当x存在两个子节点时,y是x的后继(successor),这里由于x的右子树非空,即min(x.right).
	 * 		2.增加一个记录y节点初始颜色的变量originColor,
	 * 		3.增加Nord z,用于记录y的初始位置状态。
	 * 
	 * 	这样设置y的原因与最后要进行判断的条件有关:originColor=BLACK
	 * 	a. 当Nord x不存在两个子节点时,若x.color=red,则x.left/right=black,这时将x.left/right移植到x的位置时,
	 * 		i. 黑高不改变;	ii.不存在相邻的红色节点;	对照红黑树的5条性质,都满足。此时y为x.
	 * 	b.当Nord x存在两个子节点时,这时取y=min(x),即x的后继。若y.color=red,则y的子节点为黑色。
	 * 		i.此时若x为red,即y非x的右子树,x的子节点为黑,首先y和x同色,对红黑树性质无影响,再有此时y.child将成为x.child
	 * 		的子节点,而两者都是黑色,无影响。
	 * 		ii.若x为black,则需要将y移动后染成black(代码中 y.color=x.color;),此时可保证黑高不变.由于y的子节点为
	 * 		黑,它可以接受任何颜色的父节点。
	 * 	c.若y.color=red,则y非根节点,root仍保持黑色。
	 * 
	 * @param x
	 * @return
	 */
	public Nord remove(Nord x){
		if(x!=null){
			Nord y=x,z;
			boolean originColor=y.color;
			if(x.left==null){
				z=x.right;
				transplant(x,x.right);
			}else if(x.right==null){
				z=x.left;
				transplant(x,x.left);
			}else{
				y=min(x.right);
				originColor=y.color;
				z=y.right;
				if(y==x.right){
					if(z!=null) z.parent=y;
				}else{
					transplant(y,y.right);
					y.right=x.right;
					y.right.parent=y;
				}
				transplant(x,y);
				y.left=x.left;
				y.left.parent=y;
				y.color=x.color;
			}
			if(originColor==BLACK){
				if(z!=null) {
					removeFixup(z);
				}else {
					removeFixup(x);		//当x无子节点时,以x为起点向上做fixup
				}	
			}
			return x;
		}
		return null;
	}

removeFixup函数讨论的情况:

这里写图片描述

case 1:

这里写图片描述

case 2:

这里写图片描述

case 3:

这里写图片描述

case 4:

这里写图片描述

public void removeFixup(Nord z)-删除修正函数

	/**
	 * w:z的兄弟节点
	 * 当z是左孩子时,
	 * case 1:	若w为red,则把w染黑,再把父节点z.parent染红,在对z.parent作左旋,最后把w赋为z新的兄弟节点;
	 * case 2:	若w为黑,且w的两个子节点都为黑,则将w染红,z上移,赋为z.parent;
	 * case 3:	若w为黑,w的右孩子为黑,左孩子为红,则先把w.left染黑,w染红,再对w做右旋,最后把w赋为z新的兄弟节点;
	 * case 4:	若w为黑,且w的两个子节点都为红,把z.parent的颜色赋给w,z.parent染黑,再对z.parent作左旋,z=root,退出循环
	 * 若z是右孩子,与左孩子正相反。
	 * 最后将z颜色染黑
	 * 
	 * @param z
	 * @return
	 */
	public void removeFixup(Nord z) {
		while(z!=null && z!=root && colorOf(z)==BLACK) {
			Nord w;
			if(z==leftOf(parentOf(z))) {			//若z是左孩子
				w=rightOf(parentOf(z));
				if(colorOf(w)==RED) {				//case 1
					setColor(w,BLACK);
					setColor(parentOf(z),RED);
					leftRotate(parentOf(z));
					w=rightOf(parentOf(z));			//case 1 结束
				}
				if(colorOf(leftOf(w))==BLACK && colorOf(rightOf(w))==BLACK) {	//case 2
					setColor(w,RED);
					z=parentOf(z);						//case 2 结束
				}else {
					if(colorOf(rightOf(w))==BLACK) {			//case 3
						setColor(leftOf(w),BLACK);
						setColor(w,RED);
						rightRotate(w);
						w=rightOf(parentOf(z));				//case 3 结束
					}
					setColor(w,colorOf(parentOf(z)));			//case 4
					setColor(parentOf(z),BLACK);
					setColor(rightOf(w),BLACK);
					leftRotate(parentOf(z));
					root=z;							//case 4 结束
				}
			}else {					//若z是右孩子,与左孩子正相反
				w=leftOf(parentOf(z));
				if(colorOf(w)==RED) {
					setColor(w,BLACK);
					setColor(parentOf(z),RED);
					rightRotate(parentOf(z));
					w=leftOf(parentOf(z));
				}
				if(colorOf(leftOf(w))==BLACK && colorOf(rightOf(w))==BLACK) {
					setColor(w,RED);
					z=parentOf(z);
				}else {
					if(colorOf(leftOf(w))==BLACK) {
						setColor(rightOf(w),BLACK);
						setColor(w,RED);
						leftRotate(w);
						w=leftOf(parentOf(z));
					}
					setColor(w,colorOf(parentOf(z)));
					setColor(parentOf(z),BLACK);
					setColor(leftOf(w),BLACK);
					rightRotate(parentOf(z));
					root=z;
				}
			}
		}
		setColor(z,BLACK);
	}

五、测试程序

    public void printRBTree(Nord root,int n)-凹入法打印rb树

	//打印红黑树
	public void printRBTree(Nord root,int n) {
		if(root!=null) {
			printRBTree(root.right,n+1);
			for(int i=0;i<n;i++) System.out.print("   ");
			System.out.print("-- ");
			System.out.print(root.element+":"+showColor(root)+"\n");
			printRBTree(root.left,n+1);
		}
	}

    private String showColor(Nord x)-颜色标记

	private String showColor(Nord x) {
		if(x!=null) {
			return x.color?"红":"黑";
		}else return "空";
	}

    测试程序:

	public static void main(String[] args) {
		RBTree r=new RBTree();
		r.insert(-5);
		r.remove(-5);
		System.out.println(r.getRoot());
		r.insert(-5);
		r.insert(-8);
		r.insert(-10);
		r.insert(-6);
		r.insert(5);
		r.insert(10);
		r.insert(1);
		r.insert(2);
		r.insert(0);
		r.printRBTree(r.getRoot(),0);
		r.remove(-5);
		System.out.println("===============");
		r.printRBTree(r.getRoot(),0);
		r.remove(1);
		System.out.println("===============");
		r.printRBTree(r.getRoot(),0);
		r.remove(10);
		System.out.println("===============");
		r.printRBTree(r.getRoot(),0);
		r.remove(2);
		System.out.println("===============");
		r.printRBTree(r.getRoot(),0);
		r.remove(5);
		System.out.println("===============");
		r.printRBTree(r.getRoot(),0);
	}
    结果:
null
      -- 10:黑
   -- 5:红
         -- 2:红
      -- 1:黑
         -- 0:红
-- -5:黑
      -- -6:黑
   -- -8:红
      -- -10:黑
===============
      -- 10:黑
   -- 5:红
         -- 2:红
      -- 1:黑
-- 0:黑
      -- -6:黑
   -- -8:红
      -- -10:黑
===============
      -- 10:黑
   -- 5:红
      -- 2:黑
-- 0:黑
      -- -6:黑
   -- -8:红
      -- -10:黑
===============
   -- 5:黑
      -- 2:红
-- 0:黑
      -- -6:黑
   -- -8:红
      -- -10:黑
===============
   -- 5:黑
-- 0:黑
      -- -6:黑
   -- -8:红
      -- -10:黑
===============
   -- 0:黑
      -- -6:红
-- -8:黑
   -- -10:黑



参考:https://blog.csdn.net/u010853261/article/details/54312932

https://blog.csdn.net/carechere/article/details/51749885


猜你喜欢

转载自blog.csdn.net/ever_who/article/details/80323956
今日推荐