之前一直在复习数据结构中的树,从最开始的二叉树,到二叉查找树,再到平衡二叉树(AVL),到最后的红黑树。要说复杂度,红黑树确实比之前的AVL树要复杂好多,特别是删除操作。但其实弄懂了红黑树的平衡原理,插入和删除看起来也就没那么复杂了。我主要看的是《算法导论》和《数据结构》这两本书,前者对于红黑树的描述更加准确并且提供了相应的伪代码。而我自己将原理弄懂之后,就对应着伪代码写出了红黑树的Java程序。
红黑树是一种自平衡二叉查找树,属于平衡二叉树。但它并严格意义上不是平衡二叉树,说它不严格是因为它不是严格控制左、右子树高度或节点数之差小于等于1。但红黑树高度依然是平均log(n),且最坏情况高度不会超过2log(n),这有数学证明。所以它算平衡树,只是不严格。不过严格与否并不影响数据结构的复杂度。
有了红黑树,AVL树就被放进了博物馆,因为效率的问题,红黑树得到了广泛的应用。Java集合类库中的TreeSet内部就是用的红黑树的数据结构。红黑树的插入操作最多只有两次旋转,删除操作最多三次旋转就能完成。代码如下:
节点和构造器方法:
private static final boolean RED=false;
private static final boolean BLACK=true;
private Node<T> root=null; //根节点
private Node<T> nullNode=null; //空节点,所以null节点均指向它
public RBTree()
{
nullNode=new Node<T>(null);
nullNode.left=nullNode.right=nullNode;
nullNode.color=BLACK;
}
//节点类,包含元素和高度信息
private static class Node<T>
{
private T element;
private Node<T> parent;
private Node<T> left;
private Node<T> right;
private boolean color;
public Node(T element)
{
this.element=element;
}
public Node(T element,Node<T> left,Node<T> right)
{
parent=null;
this.element=element;
this.left=left;
this.right=right;
color=RED;
}
}
插入方法和修复处理:
//插入方法
public void insert(T t)
{
if(root==null | root==nullNode) //根节点的处理,特殊对待
{
root=new Node<T>(t,nullNode,nullNode);
root.color=BLACK;
root.parent=nullNode;
return;
}
Node<T> current=root;
Node<T> parent=current;
int i=0;
while(current!=nullNode)
{
i=t.compareTo(current.element);
parent=current;
if(i>0)
current=current.right;
else
current=current.left;
}
if(i!=0) //说明是新插入的元素,进行插入操作
{
if(i>0) //插入到节点的右子节点上
{
parent.right=new Node<T>(t,nullNode,nullNode);
parent.right.parent=parent;
current=parent.right;
}
else //插入到节点的左子节点上
{
parent.left=new Node<T>(t,nullNode,nullNode);
parent.left.parent=parent;
current=parent.left;
}
}
fixupInsert(current); //修复操作
}
//插入修复操作,三种情况,最多旋转2次就满足要求
private void fixupInsert(Node<T> node)
{
if(node==nullNode)
return;
Node<T> uncle;
while(node.parent.color==RED)
{
if(node.parent==node.parent.parent.right)
{
uncle=node.parent.parent.left;
if(uncle.color==RED) //node节点的父节点和叔节点都为红色,则变颜色就行,不用旋转;这种情况才会循环
{
uncle.color=node.parent.color=BLACK;
node.parent.parent.color=RED;
node=node.parent.parent;
}
else //node节点的叔节点为黑色
{
if(node==node.parent.left) //node节点为左节点,要先进行左旋,再进行右旋
{
node=node.parent; //node上移一个节点
singleLeftRotate(node); //先左旋
}
node.parent.color=BLACK; //node的父节点颜色涂黑,node的父父节点颜色涂红
node.parent.parent.color=RED;
singleRightRotate(node.parent.parent); //在右旋
}
}
else //同上面对称
{
uncle=node.parent.parent.right;
if(uncle.color==RED) //node节点的父节点和叔节点都为红色,则变颜色就行,不用旋转
{
uncle.color=node.parent.color=BLACK;
node.parent.parent.color=RED;
node=node.parent.parent;
}
else
{
if(node==node.parent.right)
{
node=node.parent;
singleRightRotate(node);
}
node.parent.color=BLACK;
node.parent.parent.color=RED;
singleLeftRotate(node.parent.parent);
}
}
}
if(root.parent!=nullNode) //判断root节点是否有移动,有的话对root重新赋值
root=root.parent;
root.color=BLACK;
}
删除操作和修复方法:
public void remove(T t)
{
if(root==null)
return;
Node<T> current=root;
int i=1;
while((i=t.compareTo(current.element))!=0& current!=nullNode)
{
if(i>0)
current=current.right;
else
current=current.left;
}
if(i==0)
{
boolean color=RED;
if(current.left!=nullNode & current.right!=nullNode) //有两个孩子
{
Node<T> parent=current;
current=findMin(current);
parent.element=current.element;
}
Node<T> child;
if(current.right!=nullNode) //对child节点进行赋值
{
child=current.right;
}
else if(current.left!=nullNode)
{
child=current.left;
}
else
child=nullNode;
if(current.parent==nullNode) //真正被删除的节点是根节点,进行特殊处理
{
root=child;
root.color=BLACK;
return; //结束方法
}
if(current==current.parent.right)
{
current.parent.right=child;
child.parent=current.parent;
}
else
{
current.parent.left=child;
child.parent=current.parent;
}
color=current.color;
if(color==BLACK)
fixupDelete(child);
}
}
private void fixupDelete(Node<T> node) //删除的修复情况,对应《算法导论》的四种情况
{
if(node==null)
return;
Node<T> w;
while(node!=root & node.color==BLACK)
{
if(node==node.parent.right)
{
w=node.parent.left;
if(w.color==RED) //case 1
{
w.color=BLACK; //case 1
node.parent.color=RED; //case 1
singleLeftRotate(node.parent); //case 1
w=node.parent.left; //case 1
}
if(w.right.color==BLACK & w.left.color==BLACK) //case 2
{
w.color=RED; //case 2
node=node.right; //case 2
}
else
{
if(w.left.color==BLACK) //case 3
{
w.right.color=BLACK; //case 3
w.color=RED; //case 3
singleRightRotate(w); //case 3
w=w.parent.left; //case 3
}
w.color=node.parent.color; //case 4
node.parent.color=BLACK; //case 4
w.left.color=BLACK; //case 4
singleLeftRotate(node.parent);//case 4
node=root; //case 4
}
}
else
{
w=node.parent.right;
if(w.color==RED)
{
w.color=BLACK;
node.parent.color=RED;
singleRightRotate(node.parent);
w=node.parent.right;
}
if(w.left.color==BLACK & w.right.color==BLACK)
{
w.color=RED;
node=node.left;
}
else
{
if(w.right.color==BLACK)
{
w.left.color=BLACK;
w.color=RED;
singleLeftRotate(w);
w=w.parent.right;
}
w.color=node.parent.color;
node.parent.color=BLACK;
w.right.color=BLACK;
singleRightRotate(node.parent);
node=root;
}
}
}
node.color=BLACK;
}
旋转和输出方法:
private void singleRightRotate(Node<T> node) //右旋方法
{
Node<T> n=node.right;
node.right=n.left;
n.left=node;
n.parent=node.parent;
node.parent=n;
if(node==n.parent.right)
n.parent.right=n;
else
n.parent.left=n;
}
private void singleLeftRotate(Node<T> node) //左旋方法
{
Node<T> n=node.left;
node.left=n.right;
n.right=node;
n.parent=node.parent;
node.parent=n;
if(node==n.parent.right) //注意:用node来判断它在parent的左边还是右边,不能用n判断
n.parent.right=n;
else
n.parent.left=n;
}
//查找右子树中的最小值,用于删除操作
private Node<T> findMin(Node<T> node)
{
if(node==null | node==nullNode)
return null;
while(node.left!=nullNode)
{
node=node.left;
}
return node;
}
@Override
public String toString()
{
StringBuilder sb=new StringBuilder();
sb.append("[");
sb.append(print(root));
sb.append("]");
return sb.toString();
}
private String print(Node<T> node) //递归输出,同时输出节点颜色
{
if(node!=nullNode)
{
StringBuilder sb=new StringBuilder();
sb.append(print(node.left));
if(node.left!=nullNode)
sb.append(",");
sb.append(node.element+" "+((node.color==BLACK)?"黑":"红"));
if(node.right!=nullNode)
sb.append(",");
sb.append(print(node.right));
return sb.toString();
}
return "";
}
结果: