平衡查找树——红黑树
在学习红黑树的内容时应该先学习算法笔记10中二叉查找树,两者属于递进关系。
二叉查找树的缺点和2-3树的引入
二叉查找树的插入和查找都需要比较,比较的次数和树的层数有关,结点的深度决定了查找的路径的长度,查找的路径的长度就是比较的次数,所以查找的次数和树的形状有关,在普通的二叉树中,二叉树的形状是插入的顺序决定的,比如下图,同样的内容,插入顺序不同,查找时的比较次数大不相同(即使图中的极端的例子出现的概率很小但不能排除),查找键值1时需要3次比较和5次比较
很显然,完美的二叉查找树就是所有的结点到根节点的路径长度尽可能的短,也就是平衡二叉查找树,先从2-3树讲起,2-3树是指有的结点2-结点,有一个键值,两个子结点,有的结点是3-结点,有两个键值,三个子结点(类似的,如果一个结点有三个键值四个子结点则为4-结点)。2-3树中对于按照1,2,3,4,5,6的极端顺序插入形成的2-3树如下:
可以看到二叉树的层数相比极端情况变少。
从2-3树到红黑树
2-3树能够实现完美的平衡二叉树,但是2-3树的插入操作比较复杂,涉及到4-结点的拆分等,为了方便的实现2-3树的有点,使用红黑树代替实现它:树中的结点都是2-结点,但是结点之间的链接允许使用红链接和黑链接,红链接代表红链接的两端的结点其实是2-3树中的一个3-结点,黑链接代表的是2-3树中的普通连接,在这里我需要特别声明的是红链接和黑链接指的是两个结点之间的连线,在JAVA中是引用,在C++中是指针,如果指向某结点的链接为红色是我们在代码中用该节点的color属性表示指向该节点的链接是红色。之所以在声明这点是因为我学的是Robert Sedgewick的《算法第四版》,而很多其他的算法书(例如很经典的《算法导论》)和网上的红黑指的是结点的红黑,这造成了对红黑树的性质的表述不一样,但是算法的本质是一样的,这不是两种算法实现,而是不同的表述而已,我个人认为使用红链接可以形象的理解为红链接链接的两个结点是一个3-结点会有助于理解。
在红黑树中必须满足下列三点
1 红链接必须是左链接
2 没有一个节点是同时和两条红链接相连。
3 红黑树是完美的黑色平衡 的,所有叶子节点到根节点所经过的黑链接的数量是一样的(可以理解为2-3树中的平衡,黑链接是2-3树中的链接,而红链接是3-结点)
根据上面的性质,在红黑树的put()操作中,根据二叉树的判断方式找到新建节点的位置,新建节点和链接是红链接(2-3树中插入形成3-节点或者临时4-节点)。然后进行下面的修正使得红黑树平衡。
1 红链接是有链接时需要向左旋转(注意图示中的结点序号,子树根节点连接为蓝色代表着红或黑)
2 两个连续的左链接是红链接,向右旋转
3 一个节点的的左右链接都是红链接,改变颜色,同时将子树根节点的链接变为红色。
红黑树的实现
/**
* 基于红黑二叉树的符号表实现
* @author XY
*
* @param <Key> 存储的键值
* @param <Value> 键值对应的数据
*/
public class RBST<Key extends Comparable<Key>,Value> {
private static final boolean RED=true;//表示链接的颜色为红色
private static final boolean BLACK=false;
//表示链接颜色为黑色,空链接的颜色为黑色。根结点的颜色为黑色
class Node{
private int N;
private Node left;
private Node right;
private boolean color;//和二叉查找树的区别就是结点有颜色,结点的颜色是指指向此节点的链接的颜色。
private Key key;
private Value value;
public Node(Key key,Value value,int N,boolean color){
this.key=key;
this.value=value;
this.N=N;
this.color=color;
}
}
/*****************内部类***************************/
private Node root;
private StringBuffer sb;
public void put(Key key,Value value){//插入
root=put(root, key, value);
root.color=BLACK;
}
public Value get(Key key){
return get(root,key);
}
private Value get(Node node,Key key){
if(node==null) return null;
int com=key.compareTo(node.key);
if(com<0) return get(node.left, key);
else if(com>0) return get(node.right, key);
else return node.value;
}
private Node put(Node node,Key key,Value value){//插入的具体实现
if(node==null) return new Node(key, value, 1, RED);
int comp=key.compareTo(node.key);
if(comp<0) node.left=put(node.left, key, value);
else if(comp>0) node.right=put(node.right, key, value);
else node.value=value;
//上面的代码和二叉查找树的很相似,唯一的不同是建立的是红链接
if(isRed(node.right) && !isRed(node.left)) node=rotatetoleft(node);
//存在红链接为右链接,向左旋转
if(isRed(node.left) && isRed(node.left.left)) node =rotatetoright(node);
//一个结点的上下都为红链接,向右旋转
if(isRed(node.left) && isRed(node.right)) flipcolor(node);
//左右都是红链接,改变链接颜色
//上面的代码是红黑树的独有部分,是具体实现树的平衡的部分
node.N=size(node.left)+size(node.right)+1;
return node;
}
private Node rotatetoleft(Node h){//向左旋转
Node x=h;
h=x.right;
h.color=x.color;//复制此子树的根节点的颜色
x.right=h.left;
h.left=x;
x.color=RED;
return h;
}
private Node rotatetoright(Node h){//向右旋转
Node x=h;
h=x.left;
h.color=x.color;
x.left=h.right;
h.right=x;
x.color=RED;
return h;
}
private void flipcolor(Node h){//改变颜色
h.left.color=BLACK;
h.right.color=BLACK;
h.color=RED;
}
public int size(Node node){
if(node==null) return 0;
return node.N;
}
public boolean isRed(Node node){
if(node==null) return BLACK;
return node.color;
}
public void show(){
sb=new StringBuffer();
show(root);
System.out.println(sb.toString());
}
private void show(Node node){
if(node.left!=null) show(node.left);
sb.append(node.key+":"+node.value+" ");
if(node.right!=null) show(node.right);
}
public static void main(String[] args) {
RBST<Integer, String> st=new RBST<Integer, String>();//测试
st.put(4, "川大");
st.put(3, "电子科大");
st.put(1, "西交");
st.put(8, "清华");
st.put(0, "西财");
st.show();
}
}