学习红黑树

最近两次面试都被问到红黑树再见再见再见

从来没有深入学习过...很忧伤啊可怜可怜可怜

今天决定要入门学习一下红黑树奋斗奋斗奋斗


参考《算法导论》



红黑树是一种二叉查找树。

 定义红黑树的节点为RBTreeNode

有五个fields:key, color, left, right, parent


一棵红黑树应满足如下红黑性质

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

2)      根节点是黑的

3)      每个叶节点(或使用哨兵代表NIL)是黑的

4)      如果一个节点是红的,则它的两个儿子都是黑的

5)      对每个节点,从该节点到其子孙节点的所有路径上包含相同数目的黑节点

 

某个节点到达一个叶节点的路径上黑节点的数目为该节点的黑高度

红黑树的黑高度为其根节点的黑高度

 

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

所以,红黑树上的动态集合操作可以在O(lg n)时间内实现

 

插入

要插入一个新节点到红黑树中。

先按照普通二叉查找树的方法将新节点插入树中,并设置为红色。

显然,若新加入的节点的父节点为红色,这样的操作破坏了树的红黑性质。

所以需要对节点重新染色以及对树结构的改变。

需要一个改变树结构的操作,这里称为旋转。


         旋转


旋转的伪代码参见算法导论

回到插入

新节点插入之后有三种情况

称新节点为z,节点的叔叔定义为z.getParent().getParent().getLeft()(或者改成right);

z的祖父肯定是黑的(否则本身就不具有红黑性质,因为z的父亲是红的)。

 

1.      z的叔叔是红的

2.      z的叔叔是黑的而且z是其父亲的右孩子

3.      z的叔叔是黑的而且z是其父亲的左孩子

情况1:


将z的父亲和叔叔染成黑色,并将z的祖父染成红色(恢复了红黑性质4和5)

将z的祖父看作新的节点,继续完成对树的改造


情况2和3:


只凭改变节点颜色没办法恢复红黑性质5的,还需要对节点作旋转操作

对于情况2,将z改成z的父亲,对z作左旋变换,情况转变为情况3

对于情况3,将z的父亲染黑,z的祖父染红,对z的祖父作右旋变换。



睡觉睡觉睡觉成功一半了,继续~


删除

最后就是删除节点啦。

还是照普通二叉查找树的删除方法将节点删掉。

但是从红黑树中删除一个节点后,有可能会破坏树的红黑性质。

显然只有当删除节点为黑时,红黑性质会被破坏。

就必须对树进行改造。


若删除节点有孩子,令x是它的孩子(有两个孩子则选择左孩子)

否则x是删除节点的后继(它没有左孩子)的右孩子

如果x是红的,只需要简单的将其染黑就恢复了树的红黑性质

如果x是黑的则进入循环


分4种情况

1.x的兄弟是红的

2.x的兄弟是黑的,且x的兄弟的孩子都是黑的

3.x的兄弟是黑的,且x的右孩子是黑的(左孩子是红的)

4.x的兄弟是黑的,且x的右孩子是红的


情况1:


如果w是红的,那么x的父亲一定是黑的

将w染黑,x的父亲染红

左旋x的父亲,情况1转为情况2或3或4

w是红的,它的孩子是黑的,所以左旋之后,x的父亲的右孩子(也就是x的新兄弟)是黑的

更新w


情况2:


w的孩子都是黑的

将w染红,这样x的父亲就满足性质5了
把x的父亲作为新的x继续循环。
若新的x为红,跳出循环将其染黑就恢复了红黑性质,
若新的x为黑,则这条路径上仍少一个黑节点,继续循环


情况3:


w的右孩子是黑的(左孩子是红的)

将w的左孩子染黑(不会是NIL,NIL是黑的)

将w染红并右旋,更新w转为情况4


情况4:


将w的右孩子染黑,将w染成其父亲的颜色,将w的父亲染黑,并对其左旋

红黑性质已完全恢复

将x置为根节点以跳出循环


循环退出后不要忘了将x染黑。



从来不会组织语言。。到后面越来越乱,估计只有我自己能看懂

配合代码看吧


Java实现


RBTreeNode

package redblacktree;

public class RBTreeNode<T> {
	public final static int RED=0, BLACK=1;
	private T key;
	private int color;
	private RBTreeNode<T> left, right, parent;
	
	public T getKey(){ return key;}
	public void setKey(T key){ this.key=key;}
	
	public int getColor(){ return color;}
	public void setColor(int color){ this.color=color;}
	
	public RBTreeNode<T> getLeft(){ return left;}
	public void setLeft(RBTreeNode<T> left){ this.left=left;}

	public RBTreeNode<T> getRight(){ return right;}
	public void setRight(RBTreeNode<T> right){ this.right=right;}

	public RBTreeNode<T> getParent(){ return parent;}
	public void setParent(RBTreeNode<T> parent){ this.parent=parent;}
	
	public RBTreeNode(){ this.color=BLACK;}
	public RBTreeNode(T key){ this.key=key; this.color=BLACK;}
	public RBTreeNode(T key, int color){ this.key=key; this.color=color;}
}

RBTree

package redblacktree;

import java.util.Comparator;

public class RBTree<T> {
	private final RBTreeNode<T> NIL=new RBTreeNode<T>();
	private RBTreeNode<T> root;
	public RBTreeNode<T> getNIL(){ return NIL;}
	public RBTreeNode<T> getRoot(){ return root;}
	public void setRoot(RBTreeNode<T> root){
		this.root=root;
		root.setParent(NIL);
		root.setColor(RBTreeNode.BLACK);
	}
	public RBTree(RBTreeNode<T> root){ 
		this.root=root;
		root.setParent(NIL);
		root.setColor(RBTreeNode.BLACK);
		if(root.getLeft()==null)
			root.setLeft(NIL);
		if(root.getRight()==null)
			root.setRight(NIL);
	}
	
	public void leftRotated(RBTreeNode<T> x){
		RBTreeNode<T> y = x.getRight();
		if(y==NIL) return;
		x.setRight(y.getLeft());
		if(y.getLeft()!=NIL)
			y.getLeft().setParent(x);
		y.setParent(x.getParent());
		if(x.getParent()==NIL)
			root=y;
		else if(x==x.getParent().getLeft())
			x.getParent().setLeft(y);
		else 
			x.getParent().setRight(y);
		x.setParent(y);
		y.setLeft(x);
	}

	public void rightRotated(RBTreeNode<T> x){
		RBTreeNode<T> y = x.getLeft();
		if(y==NIL) return;
		x.setLeft(y.getRight());
		if(y.getRight()!=NIL)
			y.getRight().setParent(x);
		y.setParent(x.getParent());
		if(x.getParent()==NIL)
			root=y;
		else if(x==x.getParent().getLeft())
			x.getParent().setLeft(y);
		else 
			x.getParent().setRight(y);
		x.setParent(y);
		y.setRight(x);
	}
//	需要重载compare方法
	public void insert(RBTreeNode<T> z, Comparator<T> comparator){
		RBTreeNode<T> y=NIL, x=root;
		while(x!=NIL){
			y=x;
//			z的key小于x的key
			if(comparator.compare(z.getKey(), x.getKey())<0)
				x=x.getLeft();
			else
				x=x.getRight();
		}
		z.setParent(y);
		if(y==NIL)
			root=z;
		else if(comparator.compare(z.getKey(), y.getKey())<0)
			y.setLeft(z);
		else
			y.setRight(z);
		z.setLeft(NIL);
		z.setRight(NIL);
		z.setColor(RBTreeNode.RED);
		
		while(z.getParent().getColor()==RBTreeNode.RED){
			if(z.getParent()==z.getParent().getParent().getLeft()){
//				y是z的叔叔
				y=z.getParent().getParent().getRight();
//				分3种情况
//				情况1,z的叔叔是红的
				if(y.getColor()==RBTreeNode.RED){
//					将z的父亲和叔叔染黑
					z.getParent().setColor(RBTreeNode.BLACK);
					y.setColor(RBTreeNode.BLACK);
//					将z的祖父染红
					z.getParent().getParent().setColor(RBTreeNode.RED);
//					z的祖父作为新的z继续循环
					z=z.getParent().getParent();
				}
				else{
//					情况2,z的叔叔是黑的,且z是右孩子
					if(z==z.getParent().getRight()){
//						下面两行将情况2转为情况3
						z=z.getParent();
						leftRotated(z);
					}
//					情况3
					z.getParent().setColor(RBTreeNode.BLACK);
					z.getParent().getParent().setColor(RBTreeNode.RED);
					rightRotated(z.getParent().getParent());
				}
			}
			else{
//				y是z的叔叔
				y=z.getParent().getParent().getLeft();
//				分3种情况
//				情况1,z的叔叔是红的
				if(y.getColor()==RBTreeNode.RED){
//					将z的父亲和叔叔染黑
					z.getParent().setColor(RBTreeNode.BLACK);
					y.setColor(RBTreeNode.BLACK);
//					将z的祖父染红
					z.getParent().getParent().setColor(RBTreeNode.RED);
//					z的祖父作为新的z继续循环
					z=z.getParent().getParent();
				}
				else{
//					情况2,z的叔叔是黑的,且z是左孩子
					if(z==z.getParent().getLeft()){
//						下面两行将情况2转为情况3
						z=z.getParent();
						rightRotated(z);
					}
//					情况3
					z.getParent().setColor(RBTreeNode.BLACK);
					z.getParent().getParent().setColor(RBTreeNode.RED);
					leftRotated(z.getParent().getParent());
				}
			}
		}
//		最后不要忘了,把根节点染黑
		root.setColor(RBTreeNode.BLACK);
	}
	
	public RBTreeNode<T> delete(RBTreeNode<T> z){
		if(z==NIL) return NIL;
		RBTreeNode<T> x=NIL, y=NIL;
//		y不会有两个儿子
		if(z.getLeft()==NIL|| z.getRight()==NIL)
			y=z;
		else
//			z有右子树,所以y不会为NIL且y的左儿子为空
			y=succ(z);
//		这种情况下,其实y就等于z
		if(y.getLeft()!=NIL)
			x=y.getLeft();
		else
			x=y.getRight();
		x.setParent(y.getParent());
		if(y.getParent()==NIL)
			root=x;
		else if(y==y.getParent().getLeft())
			y.getParent().setLeft(x);
		else
			y.getParent().setRight(x);
		if(y!=z)
			z.setKey(y.getKey());
		if(y.getColor()==RBTreeNode.BLACK){
//			如果删除节点为黑,需要改造树
			while(x!=root && x.getColor()==RBTreeNode.BLACK){
				if(x==x.getParent().getLeft()){
//					w是x的兄弟
					RBTreeNode<T> w = x.getParent().getRight();
//					情况1:如果w是红的,那么x的父亲一定是黑的
					if(w.getColor()==RBTreeNode.RED){
//						将w染黑,x的父亲染红
						w.setColor(RBTreeNode.BLACK);
						x.getParent().setColor(RBTreeNode.RED);
//						左旋x的父亲,情况1转为情况2或3或4
//						w是红的,它的孩子是黑的,所以左旋之后,x的父亲的右孩子(也就是x的新兄弟)是黑的
						leftRotated(x.getParent());
						w=x.getParent().getRight();
					}
//					之后的情况中w一定是黑的
//					情况2:w的孩子都是黑的
					if(w.getLeft().getColor()==RBTreeNode.BLACK && w.getRight().getColor()==RBTreeNode.BLACK){
//						将w染红,这样x的父亲就满足性质5了
//						把x的父亲作为新的x继续循环。
//						若新的x为红,跳出循环将其染黑就恢复了红黑性质,
//						若新的x为黑,则这条路径上仍少一个黑节点,继续循环
						w.setColor(RBTreeNode.RED);
						x=x.getParent();
					}
					else{
//						情况3:w的右孩子是黑的(左孩子是红的)
						if(w.getRight().getColor()==RBTreeNode.BLACK){
//							将w的左孩子染黑(不会是NIL,NIL是黑的)
							w.getLeft().setColor(RBTreeNode.BLACK);
//							将w染红并右旋,更新w转为情况4
							w.setColor(RBTreeNode.RED);
							rightRotated(w);
							w=x.getParent().getRight();
						}
//						情况4
//						将w的右孩子染黑,将w染成其父亲的颜色,将w的父亲染黑,并对其左旋
						w.getRight().setColor(RBTreeNode.BLACK);
						w.setColor(w.getParent().getColor());
						w.getParent().setColor(RBTreeNode.BLACK);
						leftRotated(w.getParent());
//						红黑性质已完全恢复
						x = root;
					}
				}
				else{
//					w是x的兄弟
					RBTreeNode<T> w = x.getParent().getLeft();
//					情况1:如果w是红的,那么x的父亲一定是黑的
					if(w.getColor()==RBTreeNode.RED){
//						将w染黑,x的父亲染红
						w.setColor(RBTreeNode.BLACK);
						x.getParent().setColor(RBTreeNode.RED);
//						左旋x的父亲,情况1转为情况2或3或4
//						w是红的,它的孩子是黑的,所以右旋之后,x的父亲的左孩子(也就是x的新兄弟)是黑的
						rightRotated(x.getParent());
						w=x.getParent().getLeft();
					}
//					之后的情况中w一定是黑的
//					情况2:w的孩子都是黑的
					if(w.getLeft().getColor()==RBTreeNode.BLACK && w.getRight().getColor()==RBTreeNode.BLACK){
//						将w染红,这样x的父亲就满足性质5了
//						把x的父亲作为新的x继续循环。
//						若新的x为红,跳出循环将其染红就恢复了红黑性质,
//						若新的x为黑,则这条路径上仍少一个黑节点,继续循环
						w.setColor(RBTreeNode.RED);
						x=x.getParent();
					}
					else{
//						情况3:w的左孩子是黑的(右孩子是红的)
						if(w.getLeft().getColor()==RBTreeNode.BLACK){
//							将w的右孩子染黑(不会是NIL,NIL是黑的)
							w.getRight().setColor(RBTreeNode.BLACK);
//							将w染红并左旋,更新w转为情况4
							w.setColor(RBTreeNode.RED);
							leftRotated(w);
							w=x.getParent().getLeft();
						}
//						情况4
//						将w的左孩子染黑,将w染成其父亲的颜色,将w的父亲染黑,并对其右旋
						w.getLeft().setColor(RBTreeNode.BLACK);
						w.setColor(w.getParent().getColor());
						w.getParent().setColor(RBTreeNode.BLACK);
						rightRotated(w.getParent());
//						红黑性质已完全恢复
						x = root;
					}
				}
				x.setColor(RBTreeNode.BLACK);
			}
		}
		return y;
	}
	
	public RBTreeNode<T> succ(RBTreeNode<T> z){
//		右子树非空,则后继为右子树的最左节点
		if(z.getRight()!=NIL)
			return minimum(z.getRight());
//		右子树为空,则为z的祖先中第一个比z大的
		RBTreeNode<T> y = z.getParent();
		while(y!=NIL && z==y.getRight()){
			z=y;
			y=y.getParent();
		}
		return y;
	}
	
	public RBTreeNode<T> minimum(RBTreeNode<T> root){
		RBTreeNode<T> x = root;
		while(x.getLeft()!=NIL)
			x=x.getLeft();
		return x;
	}
	
	public static void main(String[] args){
		RBTree<Integer> tree = new RBTree<Integer>(new RBTreeNode<Integer>(0)); 
		for(int i=1; i<12; ++i){
			tree.insert(new RBTreeNode<Integer>(i), new Comparator<Integer>() {
				@Override
				public int compare(Integer arg0, Integer arg1) {
					// TODO Auto-generated method stub
					return arg0.intValue()-arg1.intValue();
				}
			});
		}
	}
}

本人对java 的学习不是很深入

这边希望大牛们指导一下

有什么简单的办法让泛型可以比较吗...

有可能这个问题本身就有问题得意得意得意





另外,红黑树的实际应用有:

Linux内核虚拟内存区域管理

STL中的map


欢迎补充~~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值