一、定义
1、结点类
树中每个结点包括五个属性:key,color,left,right和p。如果一个结点没有子结点或父结点,则其left,right 或 p 指向NIL结点。可以把NIL视为红黑树的叶结点。结点类代码如下:
private static final int BLACK = 0; //黑色
private static final int RED = 1; //红色
static class RBNode{
int key; //值
RBNode left; //左结点
RBNode right; //右结点
RBNode p; //父结点
int color; //结点颜色
RBNode(){
color = BLACK;
}
RBNode(int key){
this.key = key;
color = RED;
}
}
private RBNode NIL = new RBNode(); //替换空结点的NIL结点,其颜色为黑
2、红黑树
红黑树是满足以下性质的二叉搜索树(二叉排序树):
1.每个结点是红色或黑色的。
2.根节点是黑色的。
3.每个叶结点(NIL)是黑色的。
4.红色结点的两个子结点都是黑色的。
5.对每一个结点,从该结点到其所有后代子结点的简单路径上,均包含相同数目的黑色结点。(称为黑高)
通过对任何一条从根到叶结点的简单路径上各个结点颜色进行约束,红黑树确保没有一条路径会比其他路径长出两倍,因而是近乎平衡的。(从一个结点到其后代结点简单路径中最短的情况是所有的结点都是黑色的,最长的情况是红黑相间,由于两者的黑色结点数目要相等,所有不会有一条超过另一条的两倍)
二、红黑树的旋转
红黑树的旋转与二叉平衡树的旋转一样。忽略图中代码。
1、左旋
左旋代码:
private void leftRotation(RBNode x){
RBNode y = x.right; //y是x的右孩子
x.right = y.left; //将y的左孩子作为x的右孩子
if( y.left != NIL )
y.left.p = x; //重新设置y.left的父结点为x
//将y接在x的父结点上
if( x.p == NIL) //如果x的父结点为NIL,说明x为根结点
root = y;
else if( x == x.p.left )
x.p.left = y;
else
x.p.right = y;
y.p = x.p
//将x接在y的左子树上
y.left = x;
x.p = y;
}
2、右旋
右旋代码:
private void rightRotation(RBNode x){
RBNode y = x.left;
x.left = y.right;
if( y.right != NIL )
y.right.p = x;
if( x.p == NIL)
root = y;
else if( x == x.p.left)
x.p.left = y;
else
x.p.right = y;
y.p = x.p;
y.right = x;
x.p = y;
}
三、插入
在红黑树中插入一个结点时,插入的新结点要设置为红色(如果新结点默认为黑色,那么在一颗空的红黑树插入两个结点时,不管如何都不会满足红黑树的性质5)。插入新结点后会可能影响红黑树的性质,如果影响了红黑树的性质则需要进行调整。下面分析插入一个红色的新结点会影响红黑树的哪些性质。
1、插入一个红色结点显然不会影响性质1。
2、可能影响性质2(当插入的结点是根结点时)。
3、不会影响性质3,新结点的左右孩子为NIL,NIL为黑色结点。
4、可能影响性质4,当插入的新结点的父结点为红色时影响性质4。
5、不可能影响性质5,插入一个红色结点显然不会影响黑高。
所以在红黑树插入一个新结点只可能影响性质2或4,并且每次只可能影响其中的一个。(影响性质4则说明新结点的父结点不是根结点,因为根结点必须是黑色的)。==如果影响的是性质2,那么只需要将根结点设置为黑色即可,关键是如何处理影响性质4的情况。处理的主要思想就是上移红色结点,直到被处理掉。==可以将插入新结点后的红黑树分为如下三个情况来处理。
假设插入的新结点为z,并且z.p为z.p.p的左子树,由于违反性质4,首先可以得到z.p.p一定存在并为黑色(因为z.p为红色,根结点不是红色所以z.p必然存在父结点,又由于红色结点的子结点不能是红色所以z.p.p为黑色)。
情况一:插入结点的叔结点y为红色(z的父结点的兄弟结点)。那么可以将z.p和y的颜色都设置为黑色,并将它们的父结点设置为红色,这样既不会影响黑高,又可以解决z和z.p都是红色的问题。这实际是将红色上移了,上移红色后令z指向其祖父结点。可以看到z指向其祖父结点后依然可能会影响性质2(如果z为根结点,解决影响性质2的方法同上文)或性质4(如果z.p又为红色),此时将y指向z的新叔结点,根据y的颜色又可以分为情况一和其他的情况。如果上移红色后没有影响红黑树的性质(比如值为2的结点本来就为黑色),那么调整就结束了。
情况二:如果z的叔结点为黑色,并且z为z.p的左孩子。上图中右边的红黑树就属于情况二,对于此种情况就不再需要上移红色结点,而是对z.p进行一次左旋调整,并将z指向其原来的父结点,使之转化为情况三。
情况三:z的叔结点是红色,并且z是z.p的右子树。此时只需交换z.p和z.p.p的颜色,并对z.p.p进行一次右旋。就可以调整好红黑树。
经过情况二和三的调整后红黑树必然已经满足所有的性质。上面的三种情况是在z.p是z.p.p的左子树的情况下,如果z.p是z.p.p的右子树,那么与这三种情况对称。
代码实现:
//初始化一个值为e的结点z
public void insert(int e){
RBNode z = new RBNode(e);
z.left = NIL;
z.right = NIL;
z.p = NIL;
insert(z);
}
//插入节点z
private void insert(RBNode z){
RBNode x = root;
RBNode y = NIL;
while( x != NIL ){ //查找插入位置
y = x;
if( x.key == z.key )//已经存在值为e的结点
return;
else if( z.key < x.key )
x = x.left;
else
x = x.right;
}
//将z插入到y后面
if( y == NIL ) //说明树为空,插入的是根结点
root = z;
else if( z.key < y.key )
y.left = z;
else
y.right = z;
z.p = y;
//调整红黑树
insertFixup(z);
}
private void insertFixup(RBNode z){
RBNode y; //z的叔结点
//如果z是根结点,那么z.p为NIL,NIL的颜色为黑色,所以不会进入循环
while(z.p.color == RED){ //插入结点影响红黑树的性质4时进入循环
if( z.p == z.p.p.left ){
y = z.p.p.right;
if( y.color == RED ){ //情况一
z.p.color = BLACK;
y.color = BLACK;
z.p.p.color = RED;
z = z.p.p; //上移z结点
}
else{
if( z == z.p.right ){ //情况二
z = z.p;
leftRotation(z.p); //左旋
}
//情况三
z.p.color = BLACK; //交换颜色
z.p.p.color = RED;
z = z.p.p;
rightRotation(z); //右旋
}
}
else{ //与上面三种情况对称
y = z.p.p.left;
if( y.color == RED ){
z.p.color = BLACK;
y.color = BLACK;
z.p.p.color = RED;
z = z.p.p; //上移z结点
}
else{
if( z == z.p.left ){
z = z.p;
rightRotation(z);
}
z.p.color = BLACK;
z.p.p.color = RED;
z = z.p.p;
leftRotation(z);
}
}
}
root.color = BLACK; //影响的是性质2则直接将根结点设置为黑色
}
四、删除
红黑树的删除与二叉平衡树的删除类似,如果要删除的结点只有左子树或只有右子树或都没有,那么就直接删除该结点,并用它的左子树或右子树替代他,然后再做调整;如果要删除的结点既有左子树又有右子树那么就用它的前驱(后继)替代它,但是要把替换后的结点颜色设置成与删除结点相同,然后删除前驱(后继)后再做调整。注意删除结点的前驱一定没有右子树(后继一定没有左子树)。
这里用z表示要删除的结点,用rcolor表示最终删除的颜色,y顶替z的位置的结点,x跟踪记录图中y原来的位置,先分析使用上述方法删除一个结点后红黑树会变成什么样:
1、删除结点只有左子树,只有右子树的情况类似,没有左右子树的情况也类似(把其中一个NIL当做左子树或右子树就行)。最终消失的颜色rcolor就是z的颜色
2、删除结点既有左子树又有右子树。可以看做是把y结点移到了z的位置,而y原来的位置被x顶替了。最终消失的颜色rcolor是y之前的颜色。
那么删除一个结点会对红黑树产生什么影响呢?
1.删除一个结点不会影响性质1。
2.可能影响性质2,比如删除根节点,然后根结点的左孩子或右孩子(即图中的x结点)顶替了根结点,而它的左孩子或右孩子是红色的。
3.显然不会影响性质3。
4.可能影响性质4,上图中就影响了性质4。
5.可能影响性质5,如果最终消失的颜色是黑色那么就会由于少了一个黑色而影响性质5。
所以删除一个结点最终可能影响性质2、4和5,那么如何解决这几个问题?方法是将x结点额外加一重黑色。也就是说如果x本来为黑色的结点,那么就把它看作带了两个黑色,这样就解决了性质5的问题(如果x本来为黑色那么一定不会影响性质2和4)。如果x本来为红色,那么就将x设置为黑色,这样能直接解决2、4和5的问题(如上面两图中都可以直接把x设置为红色来解决所有的问题)。于是问题就变成了如何解决x有两重黑色的问题。解决的主要思想就是将其中的一重黑色上移,直到能被处理。(如果一重黑色上移到了根节点,那么直接抛弃这重黑色即可)。下面考虑x有双重黑色的情况,假设x结点是x.p的左结点(x == x.p.right的情况与之对称)。
情况一:x的兄弟结点w为红色。那么交换w和w.p的颜色并对x.p进行一次左移。
情况二:x的兄弟结点w为黑色,并且w的子结点都是黑色。这时可以将x的一重黑色和w的黑色上移到他们的父结点,如果父结点本来是红色的,那么直接将父结点设置为黑色即可解决双重黑色问题,如果父结点是黑色的,那么父结点将会有两重黑色,则将父结点看做新的x结点,然后接着判断新的x和它的兄弟结点属于哪种情况。
情况三:x的兄弟结点w为黑色,w的左结点为红色。那么可以通过交换w和w左结点的颜色并对w的右旋将情况三转换成情况四。
情况四:x的兄弟结点w为黑色,并且w的右结点为红色。可以通过设置w.p的颜色、w的颜色和D的颜色和旋转来实现最终调整。具体是将w的颜色设置为w.p的颜色,将w.p的颜色设置为黑色,将w的右结点设置为黑色,然后对w.p进行一次左旋。注意情况四的调整其实无关w.p原来的颜色,只需要将w.p设置为黑色即可。
通过上面的图片可以看到情况一一定是转换到情况二、三、四种的一种,而情况二则可能引起循环判断,情况三则一定能转到情况四,情况四可以通过调整颜色和旋转解决双重黑色。而进入到这四种情况的前提是x原来的颜色是黑色的(这样才会因为多加了一重黑色而变成双重黑色)。
代码如下:
public void delete(int e){
RBNode p = root;
RBNode z = NIL;
while( p != NIL ){
if( p.key == e ){
z = p;break;
}
else if( e < p.key ){
p = p.left;
}
else{
p = p.right;
}
}
if( z != NIL )
delete(z);
}
//将v接在u.p上,从而顶替掉u的位置,但此时没有把u的左右子树接到v上
private void transplant(RBNode u,RBNode v){
if( u.p == NIL ) //说明u是根结点
root = v;
else if( u == u.p.left )
u.p.left = v;
else
u.p.right = v;
v.p = u.p; //重新设置v的父结点
}
private void delete(RBNode z){
RBNode y = z;
int rcolor = y.color;
RBNode x;
if( z.right == NIL ){ //z的右子树为空的情况,也包括z的子结点都为空的情况
x = z.left;
transplant(z,z.left); //使用z.left顶替z
}
else if( z.left == NIL){ //z的左子树为空的情况
x = z.right;
transplant(z,z.right); //使用z.right顶替z
}
else{
y = z.right;
while( y.left != NIL ) //查找z的后继并赋值给y,顶替z的位置的结点是y
y = y.left;
rcolor = y.color;
x = y.right; //顶替y的位置的结点是y.right
/*
*如果y.p == z,那么y顶替z、x顶替y就是把y和x整体往上移了一个结点(可画图理解),
*并且不能把z.right接到y.right上,因为z.right就是y,如果接上去的话就相当于把y作为了y自己的右子树。
*实际上y.p == z的情况也可以用else中的代码,只不过要把把else中的"y.right = z.right"语句去掉,
*但这样就增加了许多没必要的操作。
*/
if( y.p == z)
x.p = y; //这句是为了防止x为NIL的情况
else{
transplant(y,y.right); //用y.right顶替y
y.right = z.right; //将z的右子树接在y上
y.right.p = y;
}
//transplant函数只是把y接在了z.p上,而没有把z的左子树和右子树接到y上
transplant(z,y);
y.left = z.left; //将z的左子树接到y上
y.left.p = y;
y.color = z.color; //将y的颜色设置成z原来的颜色,到这里y才完全的顶替了z。
}
if(rcolor == BLACK ) //只有真正消失的颜色为黑色时才会影响红黑树
deleteFixup(x);
}
private void deleteFixup(RBNode x){
RBNode w; //记录x的兄弟结点
while( x != root && x.color == BLACK ){ //如果x是根结点则直接抛弃一重黑色,如果x为红色则直接将x设置为黑色
if( x == x.p.left ){
w = x.p.right;
if( w.color == RED ){ //情况1
x.p.colro = RED;
w.color = BLACK;
rightRotation(x.p);
w = x.p.right;
}
if( w.left.color == BLACK && w.right.color == BLACK ){//情况2
w.color = RED;
x = x.p;
}
else{
if( w.right.color == BLACK ){ //情况3
w.left.color = BLACK;
w.color = RED;
rightRotation(w);
w = x.p.right;
}
w.color = x.p.color; //情况4
x.p.color = BLACK;
w.right.color = BLACK;
leftRotation(x.p);
x = root; //经过情况4的调整后一定符合红黑树性质了,所以直接把x设置为root
}
}
else{ //与上面对称
w = x.p.left;
if( w.color == RED ){
w.p.color = RED;
w.color = BLACK;
rightRotation(x.p);
w = x.p.left;
}
if( w.left.color == BLACK && w.right.color == BLACK){
w.color = RED;
x = x.p;
}
else{
if( w.left.color == BLACK){
w.color = RED;
w.right.color = BLACK;
leftRotation(w);
w = x.p.left;
}
w.color = x.p.color;
x.p.color = BLACK;
w.left.color = BLACK;
rightRotation(x.p);
x = root;
}
}
}
x.color = BLACK;
}
完整代码:
package trees;
import java.util.*;
public class RBTree2 {
private static final int RED = 1;
private static final int BLACK = 0;
static class RBNode{
int key;
RBNode p;
RBNode left,right;
int color;
RBNode(int key){
this.key = key;
color = RED;
}
RBNode(){
color = BLACK;
}
}
private RBNode NIL = new RBNode(); //替代空结点
private RBNode root = NIL;
//左旋
private void leftRotation(RBNode x){
RBNode y = x.right;
x.right = y.left; //将y的左子树作为x的右子树
if( y.left != NIL)
y.left.p = x;
//将y接在x.p上
if( x.p == NIL) //如果x为根节点
root = y;
else if( x == x.p.left)
x.p.left = y;
else
x.p.right = y;
y.p = x.p;
//将x接在y的左子树上
y.left = x;
x.p = y;
}
//右旋
private void rightRotation(RBNode x){
RBNode y = x.left;
x.left = y.right; //将y的右子树作为x的左子树
if( y.right != NIL)
y.right.p = x;
if(x.p == NIL)//将y接在x.p上
root = y;
else if( x == x.p.left)
x.p.left = y;
else
x.p.right = y;
y.p = x.p;
y.right = x; //将x作为y的右子树
x.p = y;
}
//在以p为根的树上查找值为e的结点
private RBNode search(RBNode p,int e){
while( p != NIL ){
if( p.key == e)
return p;
else if( e < p.key )
p = p.left;
else
p = p.right;
}
return NIL;
}
public void insert(int e){
RBNode z = new RBNode(e);
z.left = NIL;
z.right = NIL;
z.p = NIL;
insert(z);
}
//插入结点z
private void insert(RBNode z){
RBNode x = root;
RBNode y = NIL;
while( x != NIL){ //查找插入位置
y = x;
if( x.key == z.key )
return;
else if( z.key < x.key )
x = x.left;
else
x = x.right;
}
z.p = y;
if( y == NIL) //插入结点z
root = z;
else if( z.key < y.key)
y.left = z;
else
y.right = z;
insertFixup(z);
}
//插入调整,目标就是将红色结点上移
private void insertFixup(RBNode z){
RBNode y;
while( z.p.color == RED){
if(z.p == z.p.p.left){
y = z.p.p.right;
if(y.color == RED){ //说明z.p和y都为红色,那么可以将红色上移到z.p.p
z.p.color = BLACK;
y.color = BLACK;
z.p.p.color = RED;
z = z.p.p;
}
else{
if( z == z.p.right ){
z = z.p;
leftRotation(z); //左旋调整
}
z.p.color = BLACK; //交换颜色
z.p.p.color = RED;
z = z.p.p;
rightRotation(z); //右旋调整
}
}
else{
y = z.p.p.left;
if( y.color == RED){
y.color = BLACK;
z.p.color = BLACK;
z.p.p.color = RED;
z = z.p.p;
}
else{
if( z == z.p.left){
z = z.p;
rightRotation(z);
}
z.p.color = BLACK;
z.p.p.color = RED;
leftRotation(z.p.p);
}
}
}
root.color = BLACK;
}
//用v替代u,最终被删除的结点要么只有左子树要么只有右子树,要么都没有
private void transplant(RBNode u,RBNode v){
if( u.p == NIL)
root = v;
else if( u == u.p.left)
u.p.left = v;
else
u.p.right = v;
v.p = u.p;
}
//删除结点
public void delete(int e){
RBNode p = root;
RBNode z = NIL;
while( p != NIL ){ //查找要删除的结点
if( p.key == e){
z=p;break;
}
else if( e < p.key )
p = p.left;
else
p = p.right;
}
if( z != NIL)
delete(z);
}
/*
* 最终被删除的结点没有左右子树,要么只有左子树或只有右子树
* 因为z如果有左右子树,那么就用z的后继替代z,然后删除z的后继,
* 而z的后继一定没有左子树。
*/
private void delete(RBNode z){
RBNode y = z; //最终删除的结点
int rcolor = y.color; //最终删除的结点的颜色
RBNode x;
if( z.left == NIL){
x = z.right;
transplant(z,x);
}
else if( z.right == NIL){
x = z.left;
transplant(z,x);
}
else{ //用z的后继y替代z,然后删除
y = z.right;
while( y.left != NIL)
y = y.left;
rcolor = y.color; //真正删除的结点是z的后继y
x = y.right;
//当y.p == z时也可以按照else中的情况操作,但是会多了很多步骤
if(y.p == z){
x.p = y;
}
else{
transplant(y,y.right); //把y.right接在y.p上
y.right = z.right; //将z的右子树接在y上
y.right.p = y;
}
transplant(z,y); //把y接在z.p上
y.left = z.left; //把z的左子树接在y上
y.left.p = y;
y.color = z.color;
}
if( rcolor == BLACK)
deleteFixup(x);
}
private void deleteFixup(RBNode x){
RBNode w;
while( x != root && x.color == BLACK){
if(x == x.p.left){
w = x.p.right;
if( w.color == RED){ //如果w为红色,情况1,将其转换成其他情况
w.color = BLACK;
x.p.color = RED;
leftRotation(x.p);
w = x.p.right;
}
if(w.left.color == BLACK && w.right.color == BLACK){
w.color = RED;
x = x.p;
}
else{
if(w.right.color == BLACK){
w.left.color = BLACK;
w.color = RED;
rightRotation(w);
w = x.p.right;
}
w.color = x.p.color;
x.p.color = BLACK;
w.right.color = BLACK;
leftRotation(x.p);
x = root;
}
}
else{
w = x.p.left;
if( w.color == RED ){
w.p.color = RED;
w.color = BLACK;
rightRotation(x.p);
w = x.p.left;
}
if( w.left.color == BLACK && w.right.color == BLACK){
w.color = RED;
x = x.p;
}
else{
if( w.left.color == BLACK){
w.color = RED;
w.right.color = BLACK;
leftRotation(w);
w = x.p.left;
}
w.color = x.p.color;
x.p.color = BLACK;
w.left.color = BLACK;
rightRotation(x.p);
x = root;
}
}
}
x.color = BLACK;
}
public void inOrder(){
inOrder(root);
}
private void inOrder(RBNode p){
if( p != NIL){
inOrder( p.left );
System.out.print(p.key+" ");
inOrder( p.right );
}
}
public static void main(String[] args){
Random rand = new Random();
RBTree2 t = new RBTree2();
//Integer[] v = new Integer[]{73,8,65,86,56,12,36,54,45,7};
ArrayList<Integer> a = new ArrayList<>();
for(int i = 0 ; i < 20 ; i++){
int x = rand.nextInt(100);
a.add(x);
System.out.print(x+" ");
t.insert(x);
}
System.out.println();
t.inOrder();
System.out.println();
for(int i = 0;i < 20 ;i++){
int x = a.get(i);
t.delete(x);
System.out.println();
t.inOrder();
}
System.out.println("=====");
t.inOrder();
}
}