红黑树

红黑树

1、每个节点只能是红色或黑色

2、树的根始终是黑色的

3、没有两个相邻的红色节点(红色节点不能有红色父节点或红色子节点,并没有说不能出现连续的黑色节点)

4、从节点(包括根)到其任何后代Null节点(叶子节点下方挂的两个空节点,并且认为它们是黑色的)的每条路径都具有相同数量的黑色节点。

再向红黑树里面添加元素的时候,红黑树主要是通过下面这两个操作来保持平衡的:

1、 recolor(重新标记 黑色或红色)

2、 rotation(旋转)

首先是 recolor,如果重新标记不能够达到红黑树的4点要求。那么我们就需要使用rotation。

假设我们插入的新节点为 X

  1. 将新插入的节点标记为红色

  2. 如果 X 是根节点(root),则标记为黑色

  3. 如果 X 也不是root 同时X 的parent不是黑色 ;

    3.1、如果 X 的uncle(叔叔) 是红色

    1. 将parent 和uncle标记为黑色
    2. 将 grand parent(祖父)标记为红色
    3. X节点的颜色和X祖父的颜色相同,然后重复步骤2,3

    来看下图

    按照上面的公式来:

    ​ 1、 将新插入的X节点标记为红色

    ​ 2、发现 X的parent(P)同样为红色,这违反了上面的第三条原则[没有两个相邻的红色节点]

    ​ 3、发现X的 uncle(U)同样为红色

    ​ 4、 将P和U 标记为黑色

    ​ 5、将 XX的grand parent(G)标记为相同的颜色,即红色,继续重复公式2、3

    ​ 6、发现G是根节点,标记为黑色

    ​ 7、 结束

    3.2 如果X 的uncle(叔叔)是黑色,就会有下面四种情况

    ​ 1、 左左(P 是 G 的左孩子,并且 X是 P 的左孩子)

    ​ 2、 左右(P 是 G 的左孩子,并且 X是 P 的右孩子)

    ​ 3、 右右(P 是 G 的右孩子,并且 X是 P 的右孩子)

    ​ 4、 右左(P 是 G 的右孩子,并且 X是 P 的左孩子)

    先简单的演示一下怎么旋转的

    如下图的红黑树,我们插入的节点是65

    左左节点旋转(j插入节点的父节点是左节点,插入节点也是左节点):

    ​ 可以围绕祖父节点69右旋,再结合变色,步骤如下

    左右节点旋转(插入节点的父节点是左节点,插入节点是右节点)

    ​ 可以先围绕父节点66左旋,然后再围绕祖父节点69右旋,最后再将67设置为黑色,把69设置为红色

    通过上面这两个可以发现如果要插入的节点是在右节点,它比左节点多了一步 父节点左旋的操作

    如果插入的数据是在右节点,如下图,要插入节点68

    右左节点旋转(插入节点的父节点是右节点,插入节点也是左节点)

    ​ 可以先围绕父节点69右旋,接着在围绕祖父节点66左旋,最后把68节点设置为黑色,把66设置为红色。

    右右节点旋转(插入节点的父节点是右节点,插入节点也是右节点)

    可以围绕祖父节点66左旋,再把旋转后的根节点69设置为黑色,把66这个节点设置为红色

    通过上面两个发现插入的节点为左节点的话,比右节点多了一步父节点右旋的操作

红黑树在Java中的实现

 private static final boolean RED   = false;
 private static final boolean BLACK = true;

// TreeMap里面使用 Entry来保存节点的数据
 static final class Entry<K,V> implements Map.Entry<K,V> {
        K key;
        V value;
        Entry<K,V> left;
        Entry<K,V> right;
        Entry<K,V> parent;
        boolean color = BLACK;
        
        Entry(K key, V value, Entry<K,V> parent) {
            this.key = key;
            this.value = value;
            this.parent = parent;
        }
        
        .....
}

接下来看下 TreeMap的put方法

  public V put(K key, V value) {
  		//先用t来保存 root节点的数据
        Entry<K,V> t = root;
        //判断当前链表是否为空
        if (t == null) {
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);
            size = 1;
            // 记录修改次数加1
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        // 这个值在构造函数的时候赋值
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            do {
                parent = t;
                //将新插入的key和t的key进行比较
                cmp = cpr.compare(key, t.key);
                //如果新插入的key小于t.key,t等于t左边的节点
                if (cmp < 0)
                    t = t.left;
                //如果新插入的key大于t.key,t等于t的右边节点
                else if (cmp > 0)
                    t = t.right;
                else
                	//如果两个key相等,新的value覆盖原有的value,并返回原有的value
                    return t.setValue(value);
            } while (t != null);
        }
        else {
            if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
                Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        //将新插入的节点作为parent节点的子节点
        Entry<K,V> e = new Entry<>(key, value, parent);
        //如果新插入key小于parent的key,则e作为parent的左子节点
        if (cmp < 0)
            parent.left = e;
          //如果新插入key小于parent的key,则e作为parent的右子节点   
        else
            parent.right = e;
        //修复红黑树
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }

查看红黑树是如何修复的

private void fixAfterInsertion(Entry<K,V> x) {
		// 默认给红色的
        x.color = RED;
		// X节点的父节点不是根节点,并且父节点的颜色是红色,如果父节点是黑色,那就不需要修复
        while (x != null && x != root && x.parent.color == RED) {
            // 如果 x的父节点是 x 祖父节点的 左节点
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
            	// 获取 x的 祖父节点的右节点,也就是叔叔节点
                Entry<K,V> y = rightOf(parentOf(parentOf(x)));
                // 如果x的 叔叔节点是红色
                if (colorOf(y) == RED) {
                	//将X的父节点设置为黑色
                    setColor(parentOf(x), BLACK);
                    //将x 的叔叔节点设置为黑色
                    setColor(y, BLACK);
                    // 将x 的祖父节点设置为红色
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                    
                    //如果x的叔叔节点是黑色
                } else {
                	// 对应的是 左右节点旋转
                	// 如果x是其父节点的右节点
                    if (x == rightOf(parentOf(x))) {
                    	// 将x的父节点设置为x
                        x = parentOf(x);
                        //左旋转
                        rotateLeft(x);
                    }
                    //把x的父节点设置为黑色
                    setColor(parentOf(x), BLACK);
                    // 把x的祖父节点设置为红色
                    setColor(parentOf(parentOf(x)), RED);
                    //右旋转
                    rotateRight(parentOf(parentOf(x)));
                }
                // 如果 X 的父节点是其祖父节点的右节点
            } else {
            	// 获取X 的父节点的兄弟节点即 叔叔节点
                Entry<K,V> y = leftOf(parentOf(parentOf(x)));
                // 只着色的情况对应的是最开始的例子,没有旋转操作,但要对应多次变换
                //如果x的父节点的兄弟节点是红色
                if (colorOf(y) == RED) {
                	//将 x 的父节点设置为黑色
                    setColor(parentOf(x), BLACK);
                    //将x的叔叔节点设置为黑色
                    setColor(y, BLACK);
                    //将x的祖父节点设置为红色
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {// 如果x 的叔叔节点是黑色
                
                	//如果x是父节点的左节点
                    if (x == leftOf(parentOf(x))) {
                        // 将x 的父节点设为x
                        x = parentOf(x);
                        rotateRight(x);
                    }
                    //将x的父节点设为黑色
                    setColor(parentOf(x), BLACK);
                    //把x的祖父节点设为红色
                    setColor(parentOf(parentOf(x)), RED);
                    rotateLeft(parentOf(parentOf(x)));
                }
            }
        }
        //将根节点强制设为黑色
        root.color = BLACK;
    }

​ TreeMap的插入节点和普通的排序二叉树的区别在于 TreeMap插入节点之后会调用方法

fixAfterInsertion(e)来重新调整红黑树的结构来让红黑树保持平衡

​ 下面可以来看下fixAfterInsertino(e)方法的执行流程

第一种:只需变色就可保持平衡

在下面的这个树中插入节点51

当我们插入节点51之后,TreeMap的put方法执行后会得到下面的这张图

接着调用 fixAfterInsertion(e)方法,代码流程如下

当第一次进入循环之后,执行后会得到下面的红黑树结构

再把x重新赋值之后,重新进入while循环,此时x节点为45

执行上述流程后,得到下面所示的红黑树结构

这个时候 X被重新赋值为69,因为60是根节点,所以会退出while循环。在退出循环后,会再次把根节点设置为黑色,得到最终的结构如下图

最后经过两次执行while循环后,我们的红黑树会调整成现在这样的结构,这样的红黑树结构是平衡的,所以路径的黑高一致,并且没有红色节点相连的情况。

第二种: 旋转搭配变色来保持平衡

给定下面这样一颗红黑树

现在插入节点66,得到如下的树结构

之后进入fixAfterInsertion(e)方法

最终得到的红黑树的机构如下

这样红黑树就保持平衡了

红黑树的删除

​ 删除的时候需要考虑这个节点位于哪个位置,是否有左右节点

删除节点是根节点

​ 直接删除根节点即可

删除节点的左孩子和右孩子都为空

​ 直接删除当前节点即可

删除节点有一个子节点不为空

​ 这时候需要使用子节点来代替当前需要删除的节点,之后再把子节点删除即可

​ 如下图,当我们需要删除节点69的时候

首先用子节点代替当前删除节点,然后再把子节点删除

最终的红黑树结构如下面所示,这个结构的红黑树我们是不需要通过变色+旋转来保持红黑树的平衡了,因为将子节点删除后树已经是平衡的了。

还有一种场景是当我们待删除节点是黑色的,黑色的节点被删除后,树的黑高就会出现不一致的情况,这个时候就需要重新调整结构。

还是拿上面这颗删除节点后的红黑树举例,我们现在需要删除节点67。

因为67 这个节点的两个子节点都是null,所以直接删除,得到如下图所示结构:

这个时候我们树的黑高是不一致的,左边黑高是3,右边是2,所以我们需要把64节点设置为红色来保持平衡。

删除节点的两个子节点都不为空

​ 删除节点两个自及诶单都不为空的情况下。跟上面有一个节点不为空的情况也是有些类似。

同样是需要找到能代替当前节点的节点,找到后,把能替代删除节点值复制过来,然后再把替代节点删除掉

  • 先找到替代节点,也就是前驱结点后者后继节点
  • 然后再把前驱节点或者后继节点复制到当前待删除节点的位置,然后在删除前驱结点或者后继节点。

​ 前驱节点:对一棵二叉树进行中序遍历,遍历后的顺序,当前节点的前一个节点为该节点的前驱节点;

​ 后继节点:对一棵二叉树进行中序遍历,遍历后的顺序,当前节点的后一个节点为该节点的后继节点;

​ 简单点:前驱是左子树中最大的节点,后继则是右子树中最小的节点。

​ 如上图的的二叉树按照中序遍历为(43,45,49,56,58,60,64,66,68,72)那么 60的前驱结点是58后继节点是64

前驱或者后继都是最接近当前节点的节点,当我们需要删除当前节点的时候,也就是找到能替代当前节点的节点。

第一种,前驱结点为黑色节点,同时有一个非空节点

如下面一棵树,我们需要删除节点64

首先找到前驱结点,把前驱结点复制到当前节点

接着删除当前前驱结点

这个时候60和63这两个节点都是红色的,我们尝试把60这个节点设置为黑色即可使整个红黑树达到平衡。

第二种,前驱结点为黑色节点,同时子节点都为空

前驱结点是黑色的,子节点都为空,这个时候操作步骤与上面基本类似

操作步骤如下

​ 因为要删除节点64,接着找到前驱节点63,把63节点复制到当前位置,然后将前驱节点63删除掉,变色后出现黑高不一致的情况下,最后把63节点设置为黑色,把65节点设置为红色,这样就能保证红黑树的平衡。

第三种,前驱节点为红色节点,同时子节点都为空

给定下面这颗红黑树,我们需要删除节点64的时候。

同样的,我们知道64的前驱结点63,接着把63赋值到64这个位置

然后删除前驱结点

删除节点后不需要变色也不需要旋转即可保持树的平衡。

public V remove(Object key) {
		//这个是获取这个key对应的entry
        Entry<K,V> p = getEntry(key);
        if (p == null)
            return null;
		
        V oldValue = p.value;
        //删除这个节点,
        deleteEntry(p);
        return oldValue;
  }

deleteEntry(e)可以删除一个节点,并且保持树的平衡

private void deleteEntry(Entry<K,V> p) {
		//记录操作数
        modCount++;
        // 二叉树里面节点数量减一
        size--;

        // If strictly internal, copy successor's element to p and then make p
        // point to successor.
        //目标节点的左右子树不为空
        if (p.left != null && p.right != null) {
        	//获取后继节点
            Entry<K,V> s = successor(p);
            p.key = s.key;
            p.value = s.value;
            p = s;
        } // p has 2 children

        // Start fixup at replacement node, if it exists.
        //要删除的节点只有一个子节点为空
        Entry<K,V> replacement = (p.left != null ? p.left : p.right);
        if (replacement != null) {
            // Link replacement to parent
            replacement.parent = p.parent;
          //如果p是根节点
            if (p.parent == null)
                root = replacement;
            else if (p == p.parent.left)
                p.parent.left  = replacement;
            else
                p.parent.right = replacement;

            // Null out links so they are OK to use by fixAfterDeletion.
            //将目标节点的连接都清空
            p.left = p.right = p.parent = null;

            // Fix replacement
            if (p.color == BLACK)
            	//调整
                fixAfterDeletion(replacement);
        } else if (p.parent == null) { // return if we are the only node.
            root = null;
        } else { //  No children. Use self as phantom replacement and unlink.
           //目标节点的左右子树都为空
           if (p.color == BLACK)
                fixAfterDeletion(p);

            if (p.parent != null) {
                if (p == p.parent.left)
                    p.parent.left = null;
                else if (p == p.parent.right)
                    p.parent.right = null;
                p.parent = null;
            }
        }
    }

successor() 寻找节点的后继函数

static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
        if (t == null)
            return null;
            // t.右子树不空,则t的后继节点是其右子树中最小的那个元素
        else if (t.right != null) {
            Entry<K,V> p = t.right;
            while (p.left != null)
                p = p.left;
            return p;
        } else {
        	// t 的右孩子为空,则t的后继是其第一个向左走的祖先
            Entry<K,V> p = t.parent;
            Entry<K,V> ch = t;
            while (p != null && ch == p.right) {
                ch = p;
                p = p.parent;
            }
            return p;
        }
    }

fixAfterDeletion() 调整位置、颜色

private void fixAfterDeletion(Entry<K,V> x) {
    while (x != root && colorOf(x) == BLACK) {
        if (x == leftOf(parentOf(x))) {
            Entry<K,V> sib = rightOf(parentOf(x));

            if (colorOf(sib) == RED) {
                setColor(sib, BLACK);
                setColor(parentOf(x), RED);
                rotateLeft(parentOf(x));
                sib = rightOf(parentOf(x));
            }

            if (colorOf(leftOf(sib))  == BLACK &&
                colorOf(rightOf(sib)) == BLACK) {
                setColor(sib, RED);
                x = parentOf(x);
            } else {
                if (colorOf(rightOf(sib)) == BLACK) {
                    setColor(leftOf(sib), BLACK);
                    setColor(sib, RED);
                    rotateRight(sib);
                    sib = rightOf(parentOf(x));
                }
                setColor(sib, colorOf(parentOf(x)));
                setColor(parentOf(x), BLACK);
                setColor(rightOf(sib), BLACK);
                rotateLeft(parentOf(x));
                x = root;
            }
        } else { // symmetric
            Entry<K,V> sib = leftOf(parentOf(x));

            if (colorOf(sib) == RED) {
                setColor(sib, BLACK);
                setColor(parentOf(x), RED);
                rotateRight(parentOf(x));
                sib = leftOf(parentOf(x));
            }

            if (colorOf(rightOf(sib)) == BLACK &&
                colorOf(leftOf(sib)) == BLACK) {
                setColor(sib, RED);
                x = parentOf(x);
            } else {
                if (colorOf(leftOf(sib)) == BLACK) {
                    setColor(rightOf(sib), BLACK);
                    setColor(sib, RED);
                    rotateLeft(sib);
                    sib = leftOf(parentOf(x));
                }
                setColor(sib, colorOf(parentOf(x)));
                setColor(parentOf(x), BLACK);
                setColor(leftOf(sib), BLACK);
                rotateRight(parentOf(x));
                x = root;
            }
        }
    }

    setColor(x, BLACK);
}

左转、右转

/** From CLR */
    private void rotateLeft(Entry<K,V> p) {
        if (p != null) {
            Entry<K,V> r = p.right;
            p.right = r.left;
            if (r.left != null)
                r.left.parent = p;
            r.parent = p.parent;
            if (p.parent == null)
                root = r;
            else if (p.parent.left == p)
                p.parent.left = r;
            else
                p.parent.right = r;
            r.left = p;
            p.parent = r;
        }
    }

    /** From CLR */
    private void rotateRight(Entry<K,V> p) {
        if (p != null) {
            Entry<K,V> l = p.left;
            p.left = l.right;
            if (l.right != null) l.right.parent = p;
            l.parent = p.parent;
            if (p.parent == null)
                root = l;
            else if (p.parent.right == p)
                p.parent.right = l;
            else p.parent.left = l;
            l.right = p;
            p.parent = l;
        }
    }

史上最清晰的红黑树讲解(下
我画了近百张图来理解红黑树

posted @ 2020-07-01 14:33  烟雨蒙尘  阅读(188)  评论(0编辑  收藏  举报