红黑树原理

 

 

      为了能以较快的时间 O(logN) 来搜索一棵树,需要保证树总是平衡的(或者至少大部分是平衡的),这就是说对树中的每个节点在它左边的后代数目和在它右边的后代数目应该大致相等。红-黑树的就是这样的一棵平衡树。

1. 红-黑树的特征

      主要有两个特征: 1.节点都有颜色;2.在插入和删除的过程中,要遵循保持这些颜色的不同排列的规则。首先第一个特征很好解决,在节点类中一个数据字段,例如 boolean 型变量,以此来表示节点的颜色信息。第二个特征比较复杂,红-黑树有它的几个规则,如果遵循这些规则,那么树就是平衡的。红-黑树的主要规则如下:

  • 1.每个节点不是红色就是黑色的

  • 2.根节点总是黑色的

  • 3.如果节点是红色的,则它的子节点必须是黑色的(反之不一定);每个叶结点(叶结点即指树尾端NIL指针或NULL结点)是黑的

  • 4.从任意节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度

     在红-黑树中插入的节点都是红色的,这不是偶然的,因为插入一个红色节点比插入一个黑色节点违背红-黑规则的可能性更小。原因是:插入黑色节点总会改变黑色高度(违背规则4),但是插入红色节点只有一半的机会会违背规则3。另外违背规则3比违背规则4要更容易修正。当插入一个新的节点时,可能会破坏这种平衡性,那么红-黑树是如何修正的呢?

 2.平衡性的修正

     红-黑树主要通过三种方式对平衡进行修正,改变节点颜色、左旋和右旋

     红黑树的应用比较广泛,主要是用它来存储有序的数据,它的时间复杂度是O(lgn),效率非常之高。
     例如,Java集合中的TreeSet和TreeMap,C++ STL中的set、map,以及Linux虚拟内存的管理,都是通过红黑树去实现的。

变色

改变节点颜色比较容易理解,因为它违背了规则3。假设现在有个节点E,然后插入节点A和节点S,节点A在左子节点,S在右子节点,目前是平衡的。如果此时再插一个节点,那么就出现了不平衡了,因为红色节点的子节点必须为黑色,但是新插的节点是红色的。所以这时候就必须改变节点颜色了。所以我们将根的两个子节点从红色变为黑色,将父节点会从黑色变成红色。可以用如下示意图表示一下:

左旋

通常左旋操作用于将一个向右倾斜的红色链接旋转为向左链接。示意图如下:

右旋

右旋可左旋刚好相反,直接看示意图:

红-黑树的节点

红-黑树是对二叉搜索树的改进,所以其节点与二叉搜索树是差不多的,只不过在它基础上增加了一个boolean型变量来表示节点的颜色,具体看RBNode类:

public class RBNode<T extends Comparable<T>>{    boolean color; //颜色
T key;              //关键字(键值)
RBNode<T> left;     //左子节点
RBNode<T> right;    //右子节点
RBNode<T> parent;   //父节点

public RBNode(T key, boolean color, RBNode<T> parent, RBNode<T> left, RBNode<T> right) {    
    this.key = key;    
    this.color = color;
    this.parent = parent;
    this.left = left;
    this.right = right;
    }
    public T getKey() {
        return key;
    }    
)

左旋的具体实现

探讨一下左旋的具体实现:

/*************对红黑树节点x进行左旋操作 ******************/
/*
 * 左旋示意图:对节点x进行左旋
 *     p                       p
 *    /                       /
 *   x                       y
 *  / \                     / \
 * lx  y      ----->       x  ry
 *    / \                 / \
 *   ly ry               lx ly
 * 左旋做了三件事:
 * 1. 将y的左子节点赋给x的右子节点,并将x赋给y左子节点的父节点(y左子节点非空时)
 * 2. 将x的父节点p(非空时)赋给y的父节点,同时更新p的子节点为y(左或右)
 * 3. 将y的左子节点设为x,将x的父节点设为y
 */
private void leftRotate(RBNode<T> x) {
    //1. 将y的左子节点赋给x的右子节点,并将x赋给y左子节点的父节点(y左子节点非空时)
    RBNode<T> y = x.right;
    x.right = y.left;
    
    if(y.left != null) 
        y.left.parent = x;
    
    //2. 将x的父节点p(非空时)赋给y的父节点,同时更新p的子节点为y(左或右)
    y.parent = x.parent;
    
    if(x.parent == null) {
        this.root = y; //如果x的父节点为空,则将y设为父节点
    } else {
        if(x == x.parent.left) //如果x是左子节点
            x.parent.left = y; //则也将y设为左子节点
        else
            x.parent.right = y;//否则将y设为右子节点
    }
    
    //3. 将y的左子节点设为x,将x的父节点设为y
    y.left = x;
    x.parent = y;        
}

右旋具体实现

上面对右旋的概念已经有了感性的认识了,这里也不再赘述了,我们从下面的代码中结合上面的示意图,探讨一下右旋的具体实现:

/*************对红黑树节点y进行右旋操作 ******************/
/*
 * 左旋示意图:对节点y进行右旋
 *        p                   p
 *       /                   /
 *      y                   x
 *     / \                 / \
 *    x  ry   ----->      lx  y
 *   / \                     / \
 * lx  rx                   rx ry
 * 右旋做了三件事:
 * 1. 将x的右子节点赋给y的左子节点,并将y赋给x右子节点的父节点(x右子节点非空时)
 * 2. 将y的父节点p(非空时)赋给x的父节点,同时更新p的子节点为x(左或右)
 * 3. 将x的右子节点设为y,将y的父节点设为x
 */
private void rightRotate(RBNode<T> y) {
    //1. 将y的左子节点设为x的右子节点,并将y设为x右子树父节点
    RBNode<T> x = y.left;
    y.left = x.right;
    
    if(x.right != null) 
        x.right.parent = y;
    
    //2. 将y的父节点p(非空时)赋给x的父节点,同时更新p的子节点为x(左或右)
    x.parent = y.parent;
    
    if(y.parent == null) {
        this.root = x; //如果y的父节点为空
    } else {
        if(y == y.parent.right) //如果y是左子节点
            y.parent.right = x; //则也将x设为左子节点
        else
            y.parent.left = x;//否则将x设为右子节点
    }
    
    x.right = y;
    y.parent = x;        
}

插入操作

       首先,将红黑树当作一颗二叉查找树,将节点插入;然后,将节点着色为红色;最后,通过旋转和重新着色等方法来修正该树,使之重新成为一颗红黑树。详细描述如下:

       第一步: 将红黑树当作一颗二叉查找树,将节点插入。
       红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。也就意味着,树的键值仍然是有序的。此外,无论是左旋还是右旋,若旋转之前这棵树是二叉查找树,旋转之后它一定还是二叉查找树。这也就意味着,任何的旋转和重新着色操作,都不会改变它仍然是一颗二叉查找树的事实。

 

       根据被插入节点的父节点的情况,可以将"当节点z被着色为红色节点,并插入二叉树"划分为三种情况来处理。
      ① 情况说明:被插入的节点是根节点。
          处理方法:直接把此节点涂为黑色。
  ② 情况说明:被插入的节点的父节点是黑色。
      处理方法:什么也不需要做。节点被插入后,仍然是红黑树。
  ③ 情况说明:被插入的节点的父节点是红色。
      处理方法:那么,该情况与红黑树的“特性(5)”相冲突。这种情况下,被插入节点是一定存在非空祖父节点的;进一步的讲,被插入节点也一定存在叔叔节点(即使叔叔节点为空,我们也视之为存在,空节点本身就是黑色节点)。理解这点之后,我们依据"叔叔节点的情况",将这种情况进一步划分为3种情况(Case)。

  现象说明 处理策略
Case 1 当前节点的父节点是红色,且当前节点的祖父节点的另一个子节点(叔叔节点)也是红色。

(01) 将“父节点”设为黑色。
(02) 将“叔叔节点”设为黑色。
(03) 将“祖父节点”设为“红色”。
(04) 将“祖父节点”设为“当前节点”(红色节点);即,之后继续对“当前节点”进行操作。

Case 2 当前节点的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的右孩子

(01) 将“父节点”作为“新的当前节点”。
(02) 以“新的当前节点”为支点进行左旋。

Case 3 当前节点的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的左孩子

(01) 将“父节点”设为“黑色”。
(02) 将“祖父节点”设为“红色”。
(03) 以“祖父节点”为支点进行右旋。

 

       红-黑树是二叉搜索树的改进,所以插入操作的前半工作时相同的,即先找到待插入的位置,再将节点插入,先来看看插入的前半段代码:

/*********************** 向红黑树中插入节点 **********************/
public void insert(T key) {
    RBNode<T> node = new RBNode<T>(key, RED, null, null, null);
    if(node != null) 
        insert(node);
}
 
//将节点插入到红黑树中
private void insert(RBNode<T> node) {
    RBNode<T> current = null; //表示最后node的父节点
    RBNode<T> x = this.root; //用来向下搜索用的
    
    //1. 找到插入的位置
    while(x != null) {
        current = x;
        int cmp = node.key.compareTo(x.key);
        if(cmp < 0) 
            x = x.left;
        else
            x = x.right;
    }
    node.parent = current; //找到了位置,将当前current作为node的父节点
    
    //2. 接下来判断node是插在左子节点还是右子节点
    if(current != null) {
        int cmp = node.key.compareTo(current.key);
        if(cmp < 0)
            current.left = node;
        else
            current.right = node;
    } else {
        this.root = node;
    }
    
    //3. 将它重新修整为一颗红黑树
    insertFixUp(node);
}

主要看看方法里面最后一步insertFixUp操作。因为插入后可能会导致树的不平衡,insertFixUp方法里主要是分情况讨论,分析何时变色,何时左旋,何时右旋。

先从理论上分析具体的情况,然后再看insertFixUp方法的具体实现。

如果是第一次插入,由于原树为空,所以只会违反红-黑树的规则2,所以只要把根节点涂黑即可;如果插入节点的父节点是黑色的,那不会违背红-黑树的规则,什么也不需要做;但是遇到如下三种情况时,我们就要开始变色和旋转了:

  • 1.插入节点的父节点和其叔叔节点(祖父节点的另一个子节点)均为红色的;

  • 2.插入节点的父节点是红色,叔叔节点是黑色,且插入节点是其父节点的右子节点;

  • 3.插入节点的父节点是红色,叔叔节点是黑色,且插入节点是其父节点的左子节点。

下面我们先挨个分析这三种情况都需要如何操作,然后再给出实现代码。

对于情况1:插入节点的父节点和其叔叔节点(祖父节点的另一个子节点)均为红色的。此时,肯定存在祖父节点,但是不知道父节点是其左子节点还是右子节点,但是由于对称性,我们只要讨论出一边的情况,另一种情况自然也与之对应。这里考虑父节点是祖父节点的左子节点的情况,如下左图所示:

对于这种情况,将当前节点(4)的父节点(5)和叔叔节点(8)涂黑,将祖父节点(7)涂红,变成上右图所示的情况。再将当前节点指向其祖父节点,再次从新的当前节点开始算法(具体等下看下面的程序)。这样上右图就变成了情况2了。

 对于情况2:插入节点的父节点是红色,叔叔节点是黑色,且插入节点是其父节点的右子节点。我们要做的操作有:将当前节点(7)的父节点(2)作为新的节点,以新的当前节点(7)为支点做左旋操作。完成后如左下图所示,这样左下图就变成情况3了。

对于情况3:插入节点的父节点是红色,叔叔节点是黑色,且插入节点是其父节点的左子节点。我们要做的操作有:将当前节点的父节点(7)涂黑,将祖父节点(11)涂红,在祖父节点为支点做右旋操作。最后把根节点涂黑,整个红-黑树重新恢复了平衡,如右上图所示。至此,插入操作完成!

private void insertFixUp(RBNode<T> node) {  
    RBNode<T> parent, gparent; //定义父节点和祖父节点  
      
    //需要修整的条件:父节点存在,且父节点的颜色是红色  
    while(((parent = parentOf(node)) != null) && isRed(parent)) {  
        gparent = parentOf(parent);//获得祖父节点  
          
        //若父节点是祖父节点的左子节点,下面else与其相反  
        if(parent == gparent.left) {                  
            RBNode<T> uncle = gparent.right; //获得叔叔节点  
              
            //case1: 叔叔节点也是红色  
            if(uncle != null && isRed(uncle)) {  
                setBlack(parent); //把父节点和叔叔节点涂黑  
                setBlack(uncle);  
                setRed(gparent); //把祖父节点涂红  
                node = gparent; //将位置放到祖父节点处  
                continue; //继续while,重新判断  
            }  
              
            //case2: 叔叔节点是黑色,且当前节点是右子节点  
            if(node == parent.right) {  
                leftRotate(parent); //从父节点处左旋  
                RBNode<T> tmp = parent; //然后将父节点和自己调换一下,为下面右旋做准备  
                parent = node;  
                node = tmp;  
            }  
              
            //case3: 叔叔节点是黑色,且当前节点是左子节点  
            setBlack(parent);  
            setRed(gparent);  
            rightRotate(gparent);  
        } else { //若父节点是祖父节点的右子节点,与上面的完全相反,本质一样的  
            RBNode<T> uncle = gparent.left;  
              
            //case1: 叔叔节点也是红色  
            if(uncle != null & isRed(uncle)) {  
                setBlack(parent);  
                setBlack(uncle);  
                setRed(gparent);  
                node = gparent;  
                continue;  
            }  
              
            //case2: 叔叔节点是黑色的,且当前节点是左子节点  
            if(node == parent.left) {  
                rightRotate(parent);  
                RBNode<T> tmp = parent;  
                parent = node;  
                node = tmp;  
            }  
              
            //case3: 叔叔节点是黑色的,且当前节点是右子节点  
            setBlack(parent);  
            setRed(gparent);  
            leftRotate(gparent);  
        }  
    }  
      
    //将根节点设置为黑色  
    setBlack(this.root);  
}  

红黑树的删除

第一步:将红黑树当作一颗二叉查找树,将节点删除。
       这和"删除常规二叉查找树中删除节点的方法是一样的"。分3种情况:
       ① 被删除节点没有儿子,即为叶节点。那么,直接将该节点删除。
       ② 被删除节点只有一个儿子。那么,直接删除该节点,并用该节点的唯一子节点顶替它的位置。
       ③ 被删除节点有两个儿子。那么,先找出它的后继节点;然后把“它的后继节点的内容”复制给“该节点的内容”;之后,删除“它的后继节点”。在这里,后继节点相当于替身,在将后继节点的内容复制给"被删除节点"之后,再将后继节点删除。这样就巧妙的将问题转换为"删除后继节点"的情况了,下面就考虑后继节点。 在"被删除节点"有两个非空子节点的情况下,它的后继节点不可能是双子非空。既然"的后继节点"不可能双子都非空,就意味着"该节点的后继节点"要么没有儿子,要么只有一个儿子。若没有儿子,则按"情况① "进行处理;若只有一个儿子,则按"情况② "进行处理。

第二步:通过"旋转和重新着色"等一系列来修正该树,使之重新成为一棵红黑树。
       因为"第一步"中删除节点之后,可能会违背红黑树的特性。所以需要通过"旋转和重新着色"来修正该树,使之重新成为一棵红黑树。

删除操作的伪代码

RB-DELETE(T, z)
if left[z] = nil[T] or right[z] = nil[T]         
   then y ← z                                  // 若“z的左孩子” 或 “z的右孩子”为空,则将“z”赋值给 “y”;
   else y ← TREE-SUCCESSOR(z)                  // 否则,将“z的后继节点”赋值给 “y”。
if left[y] ≠ nil[T]
   then x ← left[y]                            // 若“y的左孩子” 不为空,则将“y的左孩子” 赋值给 “x”;
   else x ← right[y]                           // 否则,“y的右孩子” 赋值给 “x”。
p[x] ← p[y]                                    // 将“y的父节点” 设置为 “x的父节点”
if p[y] = nil[T]                               
   then root[T] ← x                            // 情况1:若“y的父节点” 为空,则设置“x” 为 “根节点”。
   else if y = left[p[y]]                    
           then left[p[y]] ← x                 // 情况2:若“y是它父节点的左孩子”,则设置“x” 为 “y的父节点的左孩子”
           else right[p[y]] ← x                // 情况3:若“y是它父节点的右孩子”,则设置“x” 为 “y的父节点的右孩子”
if y ≠ z                                    
   then key[z] ← key[y]                        // 若“y的值” 赋值给 “z”。注意:这里只拷贝z的值给y,而没有拷贝z的颜色!!!
        copy y's satellite data into z         
if color[y] = BLACK                            
   then RB-DELETE-FIXUP(T, x)                  // 若“y为黑节点”,则调用
return y

结合伪代码以及代码上面的说明,先理解RB-DELETE。理解RB-DELETE之后,接着对 RB-DELETE-FIXUP的伪代码进行说明

RB-DELETE-FIXUP(T, x)
while x ≠ root[T] and color[x] = BLACK  
    do if x = left[p[x]]      
          then w ← right[p[x]]                                             // 若 “x”是“它父节点的左孩子”,则设置 “w”为“x的叔叔”(即x为它父节点的右孩子)                                          
               if color[w] = RED                                           // Case 1: x是“黑+黑”节点,x的兄弟节点是红色。(此时x的父节点和x的兄弟节点的子节点都是黑节点)。
                  then color[w] ← BLACK                        ▹  Case 1   //   (01) 将x的兄弟节点设为“黑色”。
                       color[p[x]] ← RED                       ▹  Case 1   //   (02) 将x的父节点设为“红色”。
                       LEFT-ROTATE(T, p[x])                    ▹  Case 1   //   (03) 对x的父节点进行左旋。
                       w ← right[p[x]]                         ▹  Case 1   //   (04) 左旋后,重新设置x的兄弟节点。
               if color[left[w]] = BLACK and color[right[w]] = BLACK       // Case 2: x是“黑+黑”节点,x的兄弟节点是黑色,x的兄弟节点的两个孩子都是黑色。
                  then color[w] ← RED                          ▹  Case 2   //   (01) 将x的兄弟节点设为“红色”。
                       x ←  p[x]                               ▹  Case 2   //   (02) 设置“x的父节点”为“新的x节点”。
                  else if color[right[w]] = BLACK                          // Case 3: x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的左孩子是红色,右孩子是黑色的。
                          then color[left[w]] ← BLACK          ▹  Case 3   //   (01) 将x兄弟节点的左孩子设为“黑色”。
                               color[w] ← RED                  ▹  Case 3   //   (02) 将x兄弟节点设为“红色”。
                               RIGHT-ROTATE(T, w)              ▹  Case 3   //   (03) 对x的兄弟节点进行右旋。
                               w ← right[p[x]]                 ▹  Case 3   //   (04) 右旋后,重新设置x的兄弟节点。
                        color[w] ← color[p[x]]                 ▹  Case 4   // Case 4: x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的右孩子是红色的。(01) 将x父节点颜色 赋值给 x的兄弟节点。
                        color[p[x]] ← BLACK                    ▹  Case 4   //   (02) 将x父节点设为“黑色”。
                        color[right[w]] ← BLACK                ▹  Case 4   //   (03) 将x兄弟节点的右子节设为“黑色”。
                        LEFT-ROTATE(T, p[x])                   ▹  Case 4   //   (04) 对x的父节点进行左旋。
                        x ← root[T]                            ▹  Case 4   //   (05) 设置“x”为“根节点”。
       else (same as then clause with "right" and "left" exchanged)        // 若 “x”是“它父节点的右孩子”,将上面的操作中“right”和“left”交换位置,然后依次执行。
color[x] ← BLACK

再次温习一下红黑树的几个特性:
(1) 每个节点或者是黑色,或者是红色。
(2) 根节点是黑色。
(3) 每个叶子节点是黑色。 [注意:这里叶子节点,是指为空的叶子节点!]
(4) 如果一个节点是红色的,则它的子节点必须是黑色的。
(5) 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。


      前面我们将"删除红黑树中的节点"大致分为两步,在第一步中"将红黑树当作一颗二叉查找树,将节点删除"后,可能违反"特性(2)、(4)、(5)"三个特性。第二步需要解决上面的三个问题,进而保持红黑树的全部特性。
      为了便于分析,我们假设"x包含一个额外的黑色"(x原本的颜色还存在),这样就不会违反"特性(5)"。为什么呢?
      通过RB-DELETE算法,我们知道:删除节点y之后,x占据了原来节点y的位置。 既然删除y(y是黑色),意味着减少一个黑色节点;那么,再在该位置上增加一个黑色即可。这样,当我们假设"x包含一个额外的黑色",就正好弥补了"删除y所丢失的黑色节点",也就不会违反"特性(5)"。 因此,假设"x包含一个额外的黑色"(x原本的颜色还存在),这样就不会违反"特性(5)"。
      现在,x不仅包含它原本的颜色属性,x还包含一个额外的黑色。即x的颜色属性是"红+黑"或"黑+黑",它违反了"特性(1)"。

      现在,我们面临的问题,由解决"违反了特性(2)、(4)、(5)三个特性"转换成了"解决违反特性(1)、(2)、(4)三个特性"。RB-DELETE-FIXUP需要做的就是通过算法恢复红黑树的特性(1)、(2)、(4)。RB-DELETE-FIXUP的思想是:将x所包含的额外的黑色不断沿树上移(向根方向移动)

可以概括为3种情况。
① 情况说明:x是“红+黑”节点。
    处理方法:直接把x设为黑色,结束。此时红黑树性质全部恢复。
② 情况说明:x是“黑+黑”节点,且x是根。
    处理方法:什么都不做,结束。此时红黑树性质全部恢复。
③ 情况说明:x是“黑+黑”节点,且x不是根。
    处理方法:这种情况又可以划分为4种子情况。这4种子情况如下表所示:

 

现象说明 处理策略
Case 1 x是"黑+黑"节点,x的兄弟节点是红色。(此时x的父节点和x的兄弟节点的子节点都是黑节点)。

(01) 将x的兄弟节点设为“黑色”。
(02) 将x的父节点设为“红色”。
(03) 对x的父节点进行左旋。
(04) 左旋后,重新设置x的兄弟节点。

Case 2 x是“黑+黑”节点,x的兄弟节点是黑色,x的兄弟节点的两个孩子都是黑色。

(01) 将x的兄弟节点设为“红色”。
(02) 设置“x的父节点”为“新的x节点”。

Case 3 x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的左孩子是红色,右孩子是黑色的。

(01) 将x兄弟节点的左孩子设为“黑色”。
(02) 将x兄弟节点设为“红色”。
(03) 对x的兄弟节点进行右旋。
(04) 右旋后,重新设置x的兄弟节点。

Case 4 x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的右孩子是红色的,x的兄弟节点的左孩子任意颜色。

(01) 将x父节点颜色 赋值给 x的兄弟节点。
(02) 将x父节点设为“黑色”。
(03) 将x兄弟节点的右子节设为“黑色”。
(04) 对x的父节点进行左旋。
(05) 设置“x”为“根节点”。

 

删除修复情况1:当前结点是黑+黑且兄弟结点为红色(此时父结点和兄弟结点的子结点分为黑)。

解法:把父结点染成红色,把兄弟结点染成黑色,之后重新进入算法(我们只讨论当前结点是其父结点左孩子时的情况)。此变换后原红黑树性质5不变,而把问题转化为兄弟结点为黑色的情况(注:变化前,原本就未违反性质5,只是为了把问题转化为兄弟结点为黑色的情况)。 即如下代码操作:

//调用RB-DELETE-FIXUP(T, x) 的1-8行代码
 1 while x ≠ root[T] and color[x] = BLACK
 2     do if x = left[p[x]]
 3           then w ← right[p[x]]
 4                if color[w] = RED
 5                   then color[w] ← BLACK                        ▹  Case 1
 6                        color[p[x]] ← RED                       ▹  Case 1
 7                        LEFT-ROTATE(T, p[x])                    ▹  Case 1
 8                        w ← right[p[x]]                         ▹  Case 1

变化前:

       

变化后:

删除修复情况2:当前结点是黑加黑且兄弟是黑色且兄弟结点的两个子结点全为黑色。

将x的兄弟节点设为“红色”,设置“x的父节点”为“新的x节点”(此变换后性质5不变),即调用RB-INSERT-FIXUP(T, z) 的第9-10行代码操作,如下:

//调用RB-DELETE-FIXUP(T, x) 的9-11行代码
9                if color[left[w]] = BLACK and color[right[w]] = BLACK
10                   then color[w] ← RED                             ▹  Case 2
11                        x <- p[x]                                  ▹  Case 2

变化前:

 

变化后:

 

解法:把兄弟结点染红,兄弟左子结点染黑,之后再在兄弟结点为支点解右旋,之后重新进入算法。此是把当前的情况转化为情况4,而性质5得以保持,即调用RB-INSERT-FIXUP(T, z) 的第12-16行代码,如下所示:

//调用RB-DELETE-FIXUP(T, x) 的第12-16行代码
12                   else if color[right[w]] = BLACK
13                           then color[left[w]] ← BLACK          ▹  Case 3
14                                color[w] ← RED                  ▹  Case 3
15                                RIGHT-ROTATE(T, w)              ▹  Case 3
16                                w ← right[p[x]]                 ▹  Case 3

变化前:

变化后:

 

 

删除修复情况4:当前结点颜色是黑-黑色,它的兄弟结点是黑色,但是兄弟结点的右子是红色,兄弟结点左子的颜色任意。

解法:把兄弟结点染成当前结点父结点的颜色,把当前结点父结点染成黑色,兄弟结点右子染成黑色,之后以当前结点的父结点为支点进行左旋,此时算法结束,红黑树所有性质调整正确,即调用RB-INSERT-FIXUP(T, z)的第17-21行代码,如下所示:

//调用RB-DELETE-FIXUP(T, x) 的第17-21行代码
17                         color[w] ← color[p[x]]                 ▹  Case 4
18                         color[p[x]] ← BLACK                    ▹  Case 4
19                         color[right[w]] ← BLACK                ▹  Case 4
20                         LEFT-ROTATE(T, p[x])                    ▹  Case 4
21                         x ← root[T]                            ▹  Case 4

 变化前:

变化后:

 

完整源代码:

package tree;
/**
 * @description implementation of Red-Black Tree by Java
 * @author eson_15
 * @param <T>
 * @date 2016-4-18 19:27:28
 */
public class RBTree<T extends Comparable<T>> {
    private RBNode<T> root; //根节点
    private static final boolean RED = false; //定义红黑树标志
    private static final boolean BLACK = true;
    
    //内部类:节点类
    public class RBNode<T extends Comparable<T>>{
        boolean color; //颜色
        T key; //关键字(键值)
        RBNode<T> left; //左子节点
        RBNode<T> right; //右子节点
        RBNode<T> parent; //父节点
        
        public RBNode(T key, boolean color, RBNode<T> parent, RBNode<T> left, RBNode<T> right) {
            this.key = key;
            this.color = color;
            this.parent = parent;
            this.left = left;
            this.right = right;
        }
        
        public T getKey() {
            return key;
        }
        
        public String toString() {
            return "" + key + (this.color == RED? "R" : "B");
        }
    }
    
    public RBTree() {
        root = null;
    }
    
    public RBNode<T> parentOf(RBNode<T> node) { //获得父节点
        return node != null? node.parent : null;
    }
    
    public void setParent(RBNode<T> node, RBNode<T> parent) { //设置父节点
        if(node != null) 
            node.parent = parent;
    }
    
    public boolean colorOf(RBNode<T> node) { //获得节点的颜色
        return node != null? node.color : BLACK;
    }
    
    public boolean isRed(RBNode<T> node) { //判断节点的颜色
        return (node != null)&&(node.color == RED)? true : false;
    }
    
    public boolean isBlack(RBNode<T> node) {
        return !isRed(node);
    }
    
    public void setRed(RBNode<T> node) { //设置节点的颜色
        if(node != null) 
            node.color = RED;
    }
    
    public void setBlack(RBNode<T> node) {
        if(node != null) {
            node.color = BLACK;
        }
    }
     
    public void setColor(RBNode<T> node, boolean color) {
        if(node != null)
            node.color = color;
    }
     
    /***************** 前序遍历红黑树 *********************/
    public void preOrder() {
        preOrder(root);
    }
 
    private void preOrder(RBNode<T> tree) {
        if(tree != null) {
            System.out.print(tree.key + " ");
            preOrder(tree.left);
            preOrder(tree.right);
        }
    }
     
    /***************** 中序遍历红黑树 *********************/
    public void inOrder() {
        inOrder(root);
    }
 
    private void inOrder(RBNode<T> tree) {
        if(tree != null) {
             preOrder(tree.left);
             System.out.print(tree.key + " ");
             preOrder(tree.right);
         }
    }
    
    /***************** 后序遍历红黑树 *********************/
    public void postOrder() {
        postOrder(root);
    }
 
    private void postOrder(RBNode<T> tree) {
        if(tree != null) {
             preOrder(tree.left);
             preOrder(tree.right);
             System.out.print(tree.key + " ");
         }
    }
    
    /**************** 查找红黑树中键值为key的节点 ***************/
    public RBNode<T> search(T key) {
        return search(root, key);
//        return search2(root, key); //使用递归的方法,本质一样的
    }
 
    private RBNode<T> search(RBNode<T> x, T key) {
        while(x != null) {
            int cmp = key.compareTo(x.key);
            if(cmp < 0) 
                x = x.left;
            else if(cmp > 0) 
                x = x.right;
            else 
                return x;
        }
        return x;
    }
    //使用递归
    private RBNode<T> search2(RBNode<T> x, T key) {
        if(x == null)
            return x;
        int cmp = key.compareTo(x.key);
        if(cmp < 0)
            return search2(x.left, key);
        else if(cmp > 0) 
            return search2(x.right, key);
        else
            return x;
    }
    
    /**************** 查找最小节点的值  **********************/
    public T minValue() {
        RBNode<T> node = minNode(root);
        if(node != null)
            return node.key;
        return null;
    }
 
    private RBNode<T> minNode(RBNode<T> tree) {
        if(tree == null)
            return null;
        while(tree.left != null) {
            tree = tree.left;
        }
        return tree;
    }
    
    /******************** 查找最大节点的值 *******************/
    public T maxValue() {
        RBNode<T> node = maxNode(root);
        if(node != null)
            return node.key;
        return null;
    }
 
    private RBNode<T> maxNode(RBNode<T> tree) {
        if(tree == null)
            return null;
        while(tree.right != null)
            tree = tree.right;
        return tree;
    }
    
    /********* 查找节点x的后继节点,即大于节点x的最小节点 ***********/
    public RBNode<T> successor(RBNode<T> x) {
        //如果x有右子节点,那么后继节点为“以右子节点为根的子树的最小节点”
        if(x.right != null) 
            return minNode(x.right);
        //如果x没有右子节点,会出现以下两种情况:
        //1. x是其父节点的左子节点,则x的后继节点为它的父节点
        //2. x是其父节点的右子节点,则先查找x的父节点p,然后对p再次进行这两个条件的判断
        RBNode<T> p = x.parent;
        while((p != null) && (x == p.right)) { //对应情况2
            x = p;
            p = x.parent;
        }
        return p; //对应情况1
        
    }
    
    /********* 查找节点x的前驱节点,即小于节点x的最大节点 ************/
    public RBNode<T> predecessor(RBNode<T> x) {
        //如果x有左子节点,那么前驱结点为“左子节点为根的子树的最大节点”
        if(x.left != null) 
            return maxNode(x.left);
        //如果x没有左子节点,会出现以下两种情况:
        //1. x是其父节点的右子节点,则x的前驱节点是它的父节点
        //2. x是其父节点的左子节点,则先查找x的父节点p,然后对p再次进行这两个条件的判断
        RBNode<T> p = x.parent;
        while((p != null) && (x == p.left)) { //对应情况2
            x = p;
            p = x.parent;
        }
        return p; //对应情况1
    }
    
    /*************对红黑树节点x进行左旋操作 ******************/
    /*
     * 左旋示意图:对节点x进行左旋
     *     p                       p
     *    /                       /
     *   x                       y
     *  / \                     / \
     * lx  y      ----->       x  ry
     *    / \                 / \
     *   ly ry               lx ly
     * 左旋做了三件事:
     * 1. 将y的左子节点赋给x的右子节点,并将x赋给y左子节点的父节点(y左子节点非空时)
     * 2. 将x的父节点p(非空时)赋给y的父节点,同时更新p的子节点为y(左或右)
     * 3. 将y的左子节点设为x,将x的父节点设为y
     */
    private void leftRotate(RBNode<T> x) {
        //1. 将y的左子节点赋给x的右子节点,并将x赋给y左子节点的父节点(y左子节点非空时)
        RBNode<T> y = x.right;
        x.right = y.left;
        
        if(y.left != null) 
            y.left.parent = x;
        
        //2. 将x的父节点p(非空时)赋给y的父节点,同时更新p的子节点为y(左或右)
        y.parent = x.parent;
        
        if(x.parent == null) {
            this.root = y; //如果x的父节点为空,则将y设为父节点
        } else {
            if(x == x.parent.left) //如果x是左子节点
                x.parent.left = y; //则也将y设为左子节点
            else
                x.parent.right = y;//否则将y设为右子节点
        }
        
        //3. 将y的左子节点设为x,将x的父节点设为y
        y.left = x;
        x.parent = y;        
    }
    
    /*************对红黑树节点y进行右旋操作 ******************/
    /*
     * 左旋示意图:对节点y进行右旋
     *        p                   p
     *       /                   /
     *      y                   x
     *     / \                 / \
     *    x  ry   ----->      lx  y
     *   / \                     / \
     * lx  rx                   rx ry
     * 右旋做了三件事:
     * 1. 将x的右子节点赋给y的左子节点,并将y赋给x右子节点的父节点(x右子节点非空时)
     * 2. 将y的父节点p(非空时)赋给x的父节点,同时更新p的子节点为x(左或右)
     * 3. 将x的右子节点设为y,将y的父节点设为x
     */
    private void rightRotate(RBNode<T> y) {
        //1. 将y的左子节点赋给x的右子节点,并将x赋给y左子节点的父节点(y左子节点非空时)
        RBNode<T> x = y.left;
        y.left = x.right;
        
        if(x.right != null) 
            x.right.parent = y;
        
        //2. 将x的父节点p(非空时)赋给y的父节点,同时更新p的子节点为y(左或右)
        x.parent = y.parent;
        
        if(y.parent == null) {
            this.root = x; //如果x的父节点为空,则将y设为父节点
        } else {
            if(y == y.parent.right) //如果x是左子节点
                y.parent.right = x; //则也将y设为左子节点
            else
                y.parent.left = x;//否则将y设为右子节点
        }
        
        //3. 将y的左子节点设为x,将x的父节点设为y
        x.right = y;
        y.parent = x;        
    }
    
    /*********************** 向红黑树中插入节点 **********************/
    public void insert(T key) {
        RBNode<T> node = new RBNode<T>(key, RED, null, null, null);
        if(node != null) 
            insert(node);
    }
    
    //将节点插入到红黑树中,这个过程与二叉搜索树是一样的
    private void insert(RBNode<T> node) {
        RBNode<T> current = null; //表示最后node的父节点
        RBNode<T> x = this.root; //用来向下搜索用的
        
        //1. 找到插入的位置
        while(x != null) {
            current = x;
            int cmp = node.key.compareTo(x.key);
            if(cmp < 0) 
                x = x.left;
            else
                x = x.right;
        }
        node.parent = current; //找到了位置,将当前current作为node的父节点
        
        //2. 接下来判断node是插在左子节点还是右子节点
        if(current != null) {
            int cmp = node.key.compareTo(current.key);
            if(cmp < 0)
                current.left = node;
            else
                current.right = node;
        } else {
            this.root = node;
        }
        
        //3. 将它重新修整为一颗红黑树
        insertFixUp(node);
    }
 
    private void insertFixUp(RBNode<T> node) {
        RBNode<T> parent, gparent; //定义父节点和祖父节点
        
        //需要修整的条件:父节点存在,且父节点的颜色是红色
        while(((parent = parentOf(node)) != null) && isRed(parent)) {
            gparent = parentOf(parent);//获得祖父节点
            
            //若父节点是祖父节点的左子节点,下面else与其相反
            if(parent == gparent.left) {                
                RBNode<T> uncle = gparent.right; //获得叔叔节点
                
                //case1: 叔叔节点也是红色
                if(uncle != null && isRed(uncle)) {
                    setBlack(parent); //把父节点和叔叔节点涂黑
                    setBlack(uncle);
                    setRed(gparent); //把祖父节点涂红
                    node = gparent; //将位置放到祖父节点处
                    continue; //继续while,重新判断
                }
                
                //case2: 叔叔节点是黑色,且当前节点是右子节点
                if(node == parent.right) {
                    leftRotate(parent); //从父节点处左旋
                    RBNode<T> tmp = parent; //然后将父节点和自己调换一下,为下面右旋做准备
                    parent = node;
                    node = tmp;
                }
                
                //case3: 叔叔节点是黑色,且当前节点是左子节点
                setBlack(parent);
                setRed(gparent);
                rightRotate(gparent);
            } else { //若父节点是祖父节点的右子节点,与上面的完全相反,本质一样的
                RBNode<T> uncle = gparent.left;
                
                //case1: 叔叔节点也是红色
                if(uncle != null & isRed(uncle)) {
                    setBlack(parent);
                    setBlack(uncle);
                    setRed(gparent);
                    node = gparent;
                    continue;
                }
                
                //case2: 叔叔节点是黑色的,且当前节点是左子节点
                if(node == parent.left) {
                    rightRotate(parent);
                    RBNode<T> tmp = parent;
                    parent = node;
                    node = tmp;
                }
                
                //case3: 叔叔节点是黑色的,且当前节点是右子节点
                setBlack(parent);
                setRed(gparent);
                leftRotate(gparent);
            }
        }
        
        //将根节点设置为黑色
        setBlack(this.root);
    }
    
    /*********************** 删除红黑树中的节点 **********************/
    public void remove(T key) {
        RBNode<T> node;
        if((node = search(root, key)) != null)
            remove(node);
    }
    
    private void remove(RBNode<T> node) {
        RBNode<T> child, parent;
        boolean color;
        
        //1. 被删除的节点“左右子节点都不为空”的情况
        if((node.left != null) && (node.right != null)) {
            //先找到被删除节点的后继节点,用它来取代被删除节点的位置
            RBNode<T> replace = node;
            //  1). 获取后继节点
            replace = replace.right;
            while(replace.left != null) 
                replace = replace.left;
            
            //  2). 处理“后继节点”和“被删除节点的父节点”之间的关系
            if(parentOf(node) != null) { //要删除的节点不是根节点
                if(node == parentOf(node).left) 
                    parentOf(node).left = replace;
                else
                    parentOf(node).right = replace;
            } else { //否则
                this.root = replace;
            }
            
            //  3). 处理“后继节点的子节点”和“被删除节点的子节点”之间的关系
            child = replace.right; //后继节点肯定不存在左子节点!
            parent = parentOf(replace);
            color = colorOf(replace);//保存后继节点的颜色
            if(parent == node) { //后继节点是被删除节点的子节点
                parent = replace;
            } else { //否则
                if(child != null) 
                    setParent(child, parent);
                parent.left = child;
                replace.right = node.right;
                setParent(node.right, replace);
            }
            replace.parent = node.parent;
            replace.color = node.color; //保持原来位置的颜色
            replace.left = node.left;
            node.left.parent = replace;
            
            if(color == BLACK) { //4. 如果移走的后继节点颜色是黑色,重新修整红黑树
                removeFixUp(child, parent);//将后继节点的child和parent传进去
            }
            node = null;
            return;
        }
    }
    //node表示待修正的节点,即后继节点的子节点(因为后继节点被挪到删除节点的位置去了)
    private void removeFixUp(RBNode<T> node, RBNode<T> parent) {
        RBNode<T> other;
        
        while((node == null || isBlack(node)) && (node != this.root)) {
            if(parent.left == node) { //node是左子节点,下面else与这里的刚好相反
                other = parent.right; //node的兄弟节点
                if(isRed(other)) { //case1: node的兄弟节点other是红色的
                    setBlack(other);
                    setRed(parent);
                    leftRotate(parent);
                    other = parent.right;
                }
                
                //case2: node的兄弟节点other是黑色的,且other的两个子节点也都是黑色的
                if((other.left == null || isBlack(other.left)) && 
                        (other.right == null || isBlack(other.right))) {
                    setRed(other);
                    node = parent;
                    parent = parentOf(node);
                } else {
                    //case3: node的兄弟节点other是黑色的,且other的左子节点是红色,右子节点是黑色
                    if(other.right == null || isBlack(other.right)) {
                        setBlack(other.left);
                        setRed(other);
                        rightRotate(other);
                        other = parent.right;
                    }
                    
                    //case4: node的兄弟节点other是黑色的,且other的右子节点是红色,左子节点任意颜色
                    setColor(other, colorOf(parent));
                    setBlack(parent);
                    setBlack(other.right);
                    leftRotate(parent);
                    node = this.root;
                    break;
                }
            } else { //与上面的对称
                other = parent.left;
                
                if (isRed(other)) {
                    // Case 1: node的兄弟other是红色的  
                    setBlack(other);
                    setRed(parent);
                    rightRotate(parent);
                    other = parent.left;
                }
 
                if ((other.left==null || isBlack(other.left)) &&
                    (other.right==null || isBlack(other.right))) {
                    // Case 2: node的兄弟other是黑色,且other的俩个子节点都是黑色的  
                    setRed(other);
                    node = parent;
                    parent = parentOf(node);
                } else {
 
                    if (other.left==null || isBlack(other.left)) {
                        // Case 3: node的兄弟other是黑色的,并且other的左子节点是红色,右子节点为黑色。  
                        setBlack(other.right);
                        setRed(other);
                        leftRotate(other);
                        other = parent.left;
                    }
 
                    // Case 4: node的兄弟other是黑色的;并且other的左子节点是红色的,右子节点任意颜色
                    setColor(other, colorOf(parent));
                    setBlack(parent);
                    setBlack(other.left);
                    rightRotate(parent);
                    node = this.root;
                    break;
                }
            }
        }
        if (node!=null)
            setBlack(node);
    }
    
    /****************** 销毁红黑树 *********************/
    public void clear() {
        destroy(root);
        root = null;
    }
 
    private void destroy(RBNode<T> tree) {
        if(tree == null) 
            return;
        if(tree.left != null) 
            destroy(tree.left);
        if(tree.right != null) 
            destroy(tree.right);
        tree = null;
    }
 
    /******************* 打印红黑树 *********************/
    public void print() {
        if(root != null) {
            print(root, root.key, 0);
        }
    }
    /*
     * key---节点的键值
     * direction--- 0:表示该节点是根节点
     *              1:表示该节点是它的父节点的左子节点
     *              2:表示该节点是它的父节点的右子节点
     */
    private void print(RBNode<T> tree, T key, int direction) {
        if(tree != null) {
            if(0 == direction) 
                System.out.printf("%2d(B) is root\n", tree.key);
            else
                System.out.printf("%2d(%s) is %2d's %6s child\n", 
                        tree.key, isRed(tree)?"R":"b", key, direction == 1?"right":"left");
            print(tree.left, tree.key, -1);
            print(tree.right, tree.key, 1);
        }
    }
}
 
View Code

 

 

参考:

http://www.cnblogs.com/skywang12345/p/3245399.html#!comments

 https://blog.csdn.net/eson_15/article/details/51144079

https://github.com/julycoding/The-Art-Of-Programming-By-July/blob/master/ebook/zh/03.01.md

https://www.tianxiaobo.com/2018/01/11/%E7%BA%A2%E9%BB%91%E6%A0%91%E8%AF%A6%E7%BB%86%E5%88%86%E6%9E%90/

https://blog.csdn.net/linshijun33/article/details/53455149

posted on 2018-10-16 17:43  溪水静幽  阅读(196)  评论(0)    收藏  举报