从TreeMap源码理解红黑树原理


红黑树,一种二叉查找树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。

红黑树的性质


1)每个节点要么是黑色,要么是红色

2)根节点是黑色

3)每个叶子节点(空结点NIL)是黑色

4)每个红色结点的两个子结点一定都是黑色

5)任意一结点到每个叶子结点的路径都包含数量相同的黑结点(确保没有一条路径比其他路径长出2倍)

基本操作


1. 左旋

红黑树在python中的应用 treemap 红黑树_红黑树在python中的应用

// TreeMap<K, V> 源码,对以p节点为根节点的子树进行左旋操作
private void rotateLeft(Entry<K,V> p) {
    if (p != null) {
        Entry<K,V> r = p.right;   // 设r为当前节点p的右孩子
        p.right = r.left;         // 步骤1:r的左孩子设为当前结点的右孩子
        if (r.left != null)
            r.left.parent = p;
        r.parent = p.parent;      // 步骤2:将r设为当前子树的根节点
        if (p.parent == null)
            root = r;
        else if (p.parent.left == p)
            p.parent.left = r;
        else
            p.parent.right = r;
        r.left = p;              // 步骤3:将当前结点p设置为r的左孩子
        p.parent = r;
    }
}

2. 右旋

红黑树在python中的应用 treemap 红黑树_红黑树在python中的应用_02

// TreeMap<K, V> 源码,对以p节点为根节点的子树进行右旋操作
private void rotateRight(Entry<K,V> p) {
    if (p != null) {
        Entry<K,V> l = p.left;         // 设l为当前节点p的左孩子
        p.left = l.right;              // 步骤1:l的右孩子设为当前结点p的左孩子
        if (l.right != null) l.right.parent = p;
        l.parent = p.parent;           // 步骤2:将l设为当前子树的根节点
        if (p.parent == null)
            root = l;
        else if (p.parent.right == p)
            p.parent.right = l;
        else p.parent.left = l;
        l.right = p;                // 步骤3:将当前结点p设置为l的右孩子
        p.parent = l;
    }
}

3. 插入操作

// TreeMap<K, V>源码,插入操作
public V put(K key, V value) {
    Entry<K,V> t = root;  // t为红黑树的根节点
    if (t == null) {  // 当前红黑树为空的情况
        compare(key, key); // type (and possibly null) check

        root = new Entry<>(key, value, null);  // 创建新的结点
        size = 1;
        modCount++;
        return null;
    }
    int cmp;
    Entry<K,V> parent;
    // split comparator and comparable paths
    Comparator<? super K> cpr = comparator;
    // 将红黑树看作二叉排序树寻找插入的位置(插入到parent结点的子节点处)
    if (cpr != null) {  
        // 判断是否有通过构造器传入比较器,即TreeMap(Comparator<? super K> comparator)
        do {
            parent = t;
            cmp = cpr.compare(key, t.key);
            if (cmp < 0)
                t = t.left;
            else if (cmp > 0)
                t = t.right;
            else
                return t.setValue(value);
        } while (t != null);
    }
    else {  // 没有通过构造器传入比较器的情况,则使用key对象实现的Comparable接口进行比较
        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);
    }
    Entry<K,V> e = new Entry<>(key, value, parent);  // 创建新节点
    if (cmp < 0)
        parent.left = e;
    else
        parent.right = e;
    fixAfterInsertion(e);  // 对红黑树进行插入后调整操作,实树重新变为红黑树
    size++;
    modCount++;
    return null;
}
// 插入后的调整操作,插入后的调整主要看当前节点的叔叔节点(祖父结点的另一个孩子)的颜色
private void fixAfterInsertion(Entry<K,V> x) {
    x.color = RED;   // 首先将插入节点设为红保证满足性质5
    while (x != null && x != root && x.parent.color == RED) {
        if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {   // 1 父节点为祖父节点的左孩子
            Entry<K,V> y = rightOf(parentOf(parentOf(x)));   // y为当前结点的叔叔节点
            if (colorOf(y) == RED) {   // 1.1 叔叔节点为红色:变色(Case1)
                setColor(parentOf(x), BLACK);  // 将父结点设为黑色
                setColor(y, BLACK);     // 将叔叔节点设为黑色
                setColor(parentOf(parentOf(x)), RED);    // 将祖父节点设为红色
                x = parentOf(parentOf(x));    // 设置当前结点为祖父节点
            } else {            // 1.2 叔叔节点为黑色:旋转
                if (x == rightOf(parentOf(x))) { // 1.2.1如果当前节点为右孩子(Case2)
                    x = parentOf(x);    // 设置父节点为当前节点x
                    rotateLeft(x);      // 对当前节点左旋
                }                            // 1.2.2 当前结点为左孩子(Case3)
                setColor(parentOf(x), BLACK);     // 设置父节点为黑色
                setColor(parentOf(parentOf(x)), RED);  // 设置祖父节点为红色
                rotateRight(parentOf(parentOf(x)));    // 对祖父节点进行右旋
            }
        } else {  // 2 父节点为祖父节点的右孩子, 与上对称
            Entry<K,V> y = leftOf(parentOf(parentOf(x)));
            if (colorOf(y) == RED) {  // 2.1
                setColor(parentOf(x), BLACK);
                setColor(y, BLACK);
                setColor(parentOf(parentOf(x)), RED);
                x = parentOf(parentOf(x));
            } else {    // 2.2
                if (x == leftOf(parentOf(x))) {    // 2.2.1
                    x = parentOf(x);
                    rotateRight(x);
                }                // 2.2.2
                setColor(parentOf(x), BLACK);
                setColor(parentOf(parentOf(x)), RED);
                rotateLeft(parentOf(parentOf(x)));
            }
        }
    }
    root.color = BLACK;
}

针对插入后的调整操作的三种情形进行分析:(以“1 父节点为祖父节点的左孩子”为例)

Case 1 : “叔叔节点为红色”

解决方案:将父结点和叔叔节点变为黑色、祖父节点变为红色。最后将祖父节点设为当前节点。

红黑树在python中的应用 treemap 红黑树_红黑树在python中的应用_03

Case 2:“叔叔结点为黑色,且当前节点为右孩子”

解决方案:将父节点设为当前节点,然后对当前节点,进行左旋进入Case3情形。

红黑树在python中的应用 treemap 红黑树_父节点_04

Case 3:“叔叔结点为黑色,且当前节点为左孩子”

解决方案:将父节点设为黑色,祖父节点设为红色,对祖父节点进行右旋操作,调整结束。

红黑树在python中的应用 treemap 红黑树_结点_05

4. 删除操作

// TreeMap<K, V> 源码,删除操作
public V remove(Object key) {
    Entry<K,V> p = getEntry(key);  // 查找key在红黑树中的位置
    if (p == null)
        return null;

    V oldValue = p.value;
    deleteEntry(p);  // 删除找到的节点
    return oldValue;
}

private void deleteEntry(Entry<K,V> p) {
    modCount++;
    size--;
    if (p.left != null && p.right != null) {   
        // p有两个孩子的情况,用它的后继节点来代替它进行删除
        Entry<K,V> s = successor(p);
        p.key = s.key;
        p.value = s.value;
        p = s;
    }
	
    // **此时删除节点p最多只有一个孩子**,找到replacement节点放入删除p节点后的位置。
    Entry<K,V> replacement = (p.left != null ? p.left : p.right);

    if (replacement != null) {   // 此时删除节点有一个孩子节点replacement
        // 用replacement节点替换p结点的位置
        replacement.parent = p.parent; 
        if (p.parent == null)
            root = replacement;
        else if (p == p.parent.left)
            p.parent.left  = replacement;
        else
            p.parent.right = replacement;

        // 删除节点p
        p.left = p.right = p.parent = null;

        if (p.color == BLACK)  // 如果删除的结点的颜色为黑色,则破坏了性质5,需要调整
            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); // 先调整,再删除(和《算法导论》中伪代码不同,但功能一致)
		// 删除节点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;
        }
    }
}
// 删除后的调整操作,插入后的调整主要看当前节点的兄弟节点的颜色
private void fixAfterDeletion(Entry<K,V> x) {
    while (x != root && colorOf(x) == BLACK) {
        if (x == leftOf(parentOf(x))) {   // 1 当前节点x为左孩子
            Entry<K,V> sib = rightOf(parentOf(x));  // sib为x的兄弟节点

            if (colorOf(sib) == RED) {  
                // 1.1 兄弟节点为红色 -> 转化为1.2情况(Case1)
                setColor(sib, BLACK);    // 兄弟节点设为黑色
                setColor(parentOf(x), RED);   // 父节点设为红色
                rotateLeft(parentOf(x));   // 对父结点进行左旋操作(此次旋转不改变结点到每个叶子结点的路径上的黑结点数)
                sib = rightOf(parentOf(x));   // 维护sib指向x的兄弟节点
            }
			// 1.2 兄弟节点为黑色
            if (colorOf(leftOf(sib))  == BLACK &&
                colorOf(rightOf(sib)) == BLACK) {  
                // 1.2.1 sib的左右孩子都为黑色 -> 重新开始调整(Case2)
                setColor(sib, RED);   // 将兄弟节点sib设为红色
                x = parentOf(x);     // 设置x的父节点为当前节点x
                // **** 第1处循环终止位置 ****
            } else {
                if (colorOf(rightOf(sib)) == BLACK) {
                    // 1.2.2 sib的左孩子为红色,右孩子为黑色 -> 转化为1.2.3的情况(Case3)
                    setColor(leftOf(sib), BLACK);  // 将sib的左孩子设为黑色
                    setColor(sib, RED);    // 将sib设置为红色
                    rotateRight(sib);  // 对sib进行左旋(此次旋转不改变结点到每个叶子结点的路径上的黑结点数)
                    sib = rightOf(parentOf(x));  // 维护sib指向x的兄弟节点
                }   
                // 1.2.3 sib的右孩子为红色(Case4)
                setColor(sib, colorOf(parentOf(x)));  // 将sib的颜色设为x的父节点颜色
                setColor(parentOf(x), BLACK);    // x的父节点设为黑色
                setColor(rightOf(sib), BLACK);     // sib的右节点设为黑色
                rotateLeft(parentOf(x));  // 对x的父节点进行右旋(此次旋转会使到父节点的左子树中的叶子结点的路径上的黑结点数+1)
                x = root;   // x设为根结点终止循环
                // **** 第2处循环终止位置 ****
            }
        } else {  // 2 当前节点x为右孩子,与上对称
            Entry<K,V> sib = leftOf(parentOf(x));

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

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

    setColor(x, BLACK); // 最后将x设为黑色节点
}

删除后的调整是针对删除黑色节点做的,由于删除了一个黑色节点导致到删除位置路径上的黑色节点少一个,不满足性质5,调整的目标是使到该位置路径上的黑色节点增加1。

下面对上述的四种情况进行分析(以当前节点是左孩子为例):

Case1:“兄弟节点为红色”

解决方案:将父节点设为红色,兄弟节点设为黑色,对父节点进行左旋操作。

红黑树在python中的应用 treemap 红黑树_父节点_06

Case2:"兄弟节点为黑色,且兄弟节点的左右孩子都为黑色"

解决方案:将兄弟节点变为红色,并重新设置当前节点为父节点。

红黑树在python中的应用 treemap 红黑树_父节点_07

Case3:“兄弟节点为黑色,且兄弟节点的左孩子为红色,右孩子为黑色”

解决方案:将兄弟节点设为红色,兄弟节点的左孩子设为黑色,对兄弟节点进行右旋操作。进入Case4情形。

红黑树在python中的应用 treemap 红黑树_父节点_08

Case4:“兄弟节点为黑色,且兄弟节点的右孩子为红色”

解决方案:将兄弟节点设为父节点的颜色,将父节点和兄弟节点的右孩子设为黑色,对父节点进行左旋操作。

红黑树在python中的应用 treemap 红黑树_红黑树_09