最近两次面试都被问到红黑树
从来没有深入学习过...很忧伤啊
今天决定要入门学习一下红黑树
参考《算法导论》
红黑树是一种二叉查找树。
定义红黑树的节点为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
欢迎补充~~