红黑树笔记

前言

TreeMap理解的时候遇上了红黑树,每次都把自己卡的死死的。。

终于拿出决心把它给搞懂了。

 

说明

一篇不错的文章:https://www.cnblogs.com/wskwbog/p/11236136.html
本笔记中大部分资料来自于此文章,红黑树理解成2-3-4树的变形 就感觉概念上好理解了。

 

五条性质
1.每个结点都是红色或黑色的
2.根结点是黑色的(是红色最终也会转黑色)
3.所有叶子结点都是黑色的,这里的叶子结点指的是空结点,常用 NIL 表示
4.如果结点为红色,则其子结点均为黑色(红色表示可与父结点合并,子结点凑什么热闹)
5.从给定结点到其任何后代 NIL 结点的每条路径都包含相同数量的黑色结点(转成 2-4 树,所有叶子结点均在最底层)

主要是弄懂 插入和删除操作。

在这之前 先弄懂旋转
左旋转 旋转结点为p
1.结点p不为空
1.r = p的右结点 待用
2.p的右结点 = r的左结点
3.如果r的左结点 != null,r的左结点的父结点 = p
4.r的父结点 = p的父结点
5.如果p的父结点 == null,根结点设为 r
6.否则p的父结点的左结点 == p,p的父结点的左结点 = r
7.否则,p的父结点的右结点 = r
8.r的左结点 = p
9.p的父结点 = r

根据上文写出右旋转代码

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.left == p) {
			p.parent.left = l;
		} else {
			p.parent.right = l;
		}
		l.left = p;
		p.parent = l;
	}
}

左右旋转OK

现在看插入
假设待插入结点为N P是N的父结点 G是N的祖父结点 U是N的叔叔结点
待插入的点默认都是红色

1.N是根结点,即红黑树的第一个结点
2.N的父结点(P)为黑色
不影响红黑树的性质,不会打破平衡,直接插入
3.P是红色的(不是根结点),它的兄弟结点U也为红色
P和U变成黑色,G变成红色,若G是根结点,直接变黑,否则递归向上检查是否平衡
4.P为红色,而U为黑色
P是G的左孩子,若N是P的左孩子,那么将祖父结点G右旋转即可
P是G的左孩子,若N是P的右孩子,那么P先左旋转,然后再将祖父结点G右旋转

P是G的右孩子,若N是P的右孩子,那么将祖父结点G左旋转即可
P是G的右孩子,若N是P的左孩子,那么P先右旋转,然后再将祖父结点G左旋转

旋转完后向上递归染色

/** From CLR */
    private void fixAfterInsertion(Entry<K,V> x) {
        x.color = RED; // 新增的x 都是红色

        while (x != null && x != root && x.parent.color == RED) { // 当x不为空 而且x不为root 而且x的父结点P是红色
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) { // P == GL 父结点是祖父结点的左结点
                Entry<K,V> y = rightOf(parentOf(parentOf(x))); // y 为 U 叔叔结点
                if (colorOf(y) == RED) {	//P红 U 为红色
                    setColor(parentOf(x), BLACK); // P 变黑
                    setColor(y, BLACK);			// U 也变黑
                    setColor(parentOf(parentOf(x)), RED); // G 变红
                    x = parentOf(parentOf(x)); // x 变为 祖父结点 这里用于循环判读 如果G不是根节点 会向上递归
                } else {			//P红 U为黑色
                    if (x == rightOf(parentOf(x))) { // x 如果是 PR
                        x = parentOf(x);		// x 变为 P
                        rotateLeft(x);			// x左旋 相当于P左旋
                    }
                    setColor(parentOf(x), BLACK); // P 设置为黑
                    setColor(parentOf(parentOf(x)), RED); // 祖父结点设为红色
                    rotateRight(parentOf(parentOf(x)));  // 祖父结点右旋
                }
            } else {					// P == GR 父结点是祖父结点的右结点
                Entry<K,V> y = leftOf(parentOf(parentOf(x)));
                if (colorOf(y) == RED) {
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    if (x == leftOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateRight(x);
                    }
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateLeft(parentOf(parentOf(x)));
                }
            }
        }
        root.color = BLACK;
    }

现在看删除
先看2-3-4树结点删除
1.如果元素K是内部结点,并且在一个至少有2个key的多值叶子结点内部,则只需从结点中删除K
2.如果元素K是内部结点,并且有左孩子和右孩子,那么:
2.1如果左孩子至少有2个key,那么找到一个最大值替换K,然后删除这个最大值
2.2如果右孩子至少有2个key,那么找到一个最小值替换K,然后删除这个最小值
2.3如果两个孩子都只有1个key,那么将K下沉,与其子女合并,形成一个至少有2个key的结点,最后再删除K
3.如果元素K不是内部结点,所在结点只有它1个key,那么根据以下情况,最终会转成情况1或情况2
3.1如果它的兄弟结点内至少有2个key,那么选择一个推到父结点中,再把旧的父结点下沉和K合并
3.2如果它的兄弟结点也只有1个key,那么将父结点下沉,与其子女合并,再删除K,所以,此时需要父结点至少有2个key,如果没有那么在父结点上递归按情况3处理。
以上的情况,有一个前提,在遍历查找待删除结点时,必须保证路过的结点都至少有2个key,不是的话就需要合并结点。
假设待删除结点为M,如果有非叶子结点,称为C,那么有两种比较简单的删除情况

1.M为红色结点,那么它必是叶子结点,直接删除即可,因为如果它有一个黑色的非叶子结点,那么就违反了性质5,通过M向左或向右的路径黑色结点不等
2.M是黑色而C是红色,只需要让C替换到M的位置,并变成黑色即可,或者说交换C和M的值,并删除C

3.M和C都是黑色,此时M肯定是叶子结点,而C肯定是NIL结点,如果不是这样的情况将违反性质5
一个黑色结点被删除会打破平衡,需要找一个结点填补这个空缺,假设待删除结点为M,删除后它的位置上就变成了NIL结点,为了方便描述,这个结点记为N
P表示N的父结点,S表示N兄弟结点,S如果存在左右孩子,分别使用SL和SR表示
3.1 N是根结点
删除后,N变成了根结点,也就是说删除前只有M这一个结点,直接删除即可。
3.2 P黑S红
S是红色,那么它必有两个孩子结点,且都为黑色,而且P也肯定是黑色。此时,交换P和S的颜色,然后对P左旋转。
结点N的父结点P变成了红色,兄弟结点变成了SL,此时就可以按照情况4、5、6继续处理。
3.3 P黑S黑
P是黑色,S也是黑色,并且S也没有非空的孩子结点。此时,直接将S变成红色,那么经过S的路径也就少了一个黑色结点,整体上就导致经过P的路径比原来少了一个黑色结点,
把不平衡状态从结点N转移到了结点P,就可以把P按情况1处理,直到遇到根结点,以此形成递归
3.4 P红S黑
P是红色,S是黑色,并且S也没有非空的孩子结点。此时,只要交换P和S的颜色,正好填补了少一个黑色结点的空缺,也就是恢复了平衡的状态
3.5 P任意S黑SL红
P任意颜色,S黑色,S的左孩子红色(S有右孩子也是红色)。此时,对S右旋转,并交换S和SL的颜色,其实就是把这种情况,转成了情况6进行处理
3.6 P任意S黑SR红
P任意颜色,S黑色,S的右孩子红色(S有左孩子也是红色)。此时,对P左旋转,并交换P和S的颜色,并将SR变成黑色
此时恢复平衡的状态,无论P之前是什么颜色,N都比之前多了一个黑色父结点。假设P原先是红色的,现在变成了黑色;假设原先是黑色的,现在P又多了一个黑色的父结点S
所以,无论怎样,经过结点N路径增加了一个黑色结点。
以上6中情况,结点N都是左孩子,如果是右孩子,只需把左右对调即可。

  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.
		// 如果严格是内部的,则将继承者的元素复制到p,然后生成p指向后续任务。
        if (p.left != null && p.right != null) {
            Entry<K,V> s = successor(p); // 必存在右结点,因此这个方法返回的s为p右结点里的最小值的结点
			// 要删除的结点的值 key 和 value 都被替换了。
            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;
            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.
	    // 空链接,以便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) { // 将p的链接断开
                if (p == p.parent.left)
                    p.parent.left = null;
                else if (p == p.parent.right)
                    p.parent.right = null;
                p.parent = null;
            }
        }
    }

  

  /**
     * Returns the successor of the specified Entry, or null if no such.
	 * 返回指定项的后续项,如果没有,则返回空值。
     */
    static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
        if (t == null)
            return null;
        else if (t.right != null) {
            Entry<K,V> p = t.right;
            while (p.left != null)
                p = p.left;
            return p;  // 如果t右结点存在,返回右结点中的最小值的结点p
        } else {
            Entry<K,V> p = t.parent;
            Entry<K,V> ch = t;
            while (p != null && ch == p.right) {
                ch = p;
                p = p.parent;
            }
            return p;  // 如果t右结点不存在,一直找到父结点中最小的结点
        }
    }

  

  /** From CLR */
    private void fixAfterDeletion(Entry<K,V> x) {
        while (x != root && colorOf(x) == BLACK) {  // 当替换结点x 不为根结点 并且 替换结点为黑色
            if (x == leftOf(parentOf(x))) {	// 如果x为PL
                Entry<K,V> sib = rightOf(parentOf(x)); // x的S

                if (colorOf(sib) == RED) {	// S为红色 情况3.2
                    setColor(sib, BLACK);	// S变成黑色
                    setColor(parentOf(x), RED); // P变成红色
                    rotateLeft(parentOf(x));	// P左旋转
                    sib = rightOf(parentOf(x)); // S变为PR
                }
				
				// 在这里的时候S都是黑色
                if (colorOf(leftOf(sib))  == BLACK &&
                    colorOf(rightOf(sib)) == BLACK) {	// 如果S的SL,SR都是黑色
                    setColor(sib, RED);	// S直接变红
                    x = parentOf(x); // 替换结点x 设为父结点  // 循环后按 情况3.2处理了
                } else {
                    if (colorOf(rightOf(sib)) == BLACK) { // 如果SR是黑色 此时SL为红色或者不存在
                        setColor(leftOf(sib), BLACK);	// SL变成黑色	
                        setColor(sib, RED);				// S变成红色
                        rotateRight(sib);				// S右旋转
                        sib = rightOf(parentOf(x));		// S变为PR  对应了情况3.5
                    }
                    setColor(sib, colorOf(parentOf(x))); // S为P的颜色 情况3.6
                    setColor(parentOf(x), BLACK);	// P设置为黑色
                    setColor(rightOf(sib), BLACK);	// SR设置为黑色
                    rotateLeft(parentOf(x));	// P左旋
                    x = root;					// 替换结点 变成 根结点退出循环
                }
            } else { // symmetric 对称的 // 如果x为父结点的右节点
                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);
    }

  

小结:

红黑树这东西慢慢来理解,反而也没想象中那么困难。概念得懂。

也感谢大神写的文章,让我有个更好理解的方式。

还要继续努力啊~

 

 

posted @ 2019-07-30 20:39  亮亮亮亮锦。  阅读(148)  评论(0编辑  收藏  举报