TreeMap红黑树源码分析

TreeMap底层以黑红树数据结构存储数据,实现了Map.Entry接口,key/value记录实际数据,left/right/parent记录左右子节点和父节点,color标识节点颜色,只有红黑两种颜色,所以使用boolean类型。

    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;
    }

先来回滚下红黑树的特性,红黑树是一种自平衡的二叉查找树,它在二叉查找树的基础上又具备如下特征:

  • 节点是红色或黑色。
  • 根节点是黑色。
  • 每个叶子节点都是黑色的空节点(NIL节点)。
  • 不能有两个连续的红色节点 (有红必有黑,红红不相连)
  • 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。(相同的黑高)

 

左旋: 

1.左旋的目标节点X往左下沉

2.右子节点替代它的位置

3.右子节点的左子树转移到目标节点的右子树上(因为Y>b>X,仍然满足平衡二叉树特性)

右旋:

 

下面主要解析下红黑树插入/删除后重新平衡的源码。

/** From CLR */
private void rotateLeft(Entry<K,V> p) { 
// 把p往左下调整一级;
// p的右子节点替代p的位置,
// p的右子节点的左子树挂在p的右子树上;
// 调整受影响节点的指针
    if (p != null) { 
        Entry<K,V> r = p.right;  // r记录p的右子节点
        p.right = r.left;// 将r的左子树挂在p的右子树上
        if (r.left != null)//如果r的左子树不空,则把父节点指针指向p
            r.left.parent = p;
        r.parent = p.parent;//r替换p的位置,r的父节点指针指向p的父节点
        if (p.parent == null)//如果p的父节点为空,则将r置为root
            root = r;
        else if (p.parent.left == p)//如果p是父节点的左子节点,则将p的父节点的左子节点置为r
            p.parent.left = r;
        else // 否则将右子节点置为r
            p.parent.right = r;
        r.left = p; // r的左节点置为p
        p.parent = r; // p的父节点置为r
    }
}
// 右旋逻辑类似,不再贴出

下面是插入节点后的调整动作,特别关注标注2,如果叔叔节点是黑色而父节点是红色,那说明当前的x并非存插入节点,而是由于子树上有新插入节点后调整导致的x由黑变红,所以x以下的黑高和y以下的黑高(含y)是相等的,理解这一点特别重要。

/** From CLR */
private void fixAfterInsertion(Entry<K,V> x) {
    x.color = RED;

    while (x != null && x != root && x.parent.color == RED) { // 进入循环条件,x不空也不是root,且父节点和x都是红色
        if (parentOf(x) == leftOf(parentOf(parentOf(x)))) { // 如果x的父节点是其祖父节点的左子节点
            Entry<K,V> y = rightOf(parentOf(parentOf(x))); // 取y=x的祖父节点的右子节点
            if (colorOf(y) == RED) { //1. 如果y是红色,则已满足黑路径平衡条件
                setColor(parentOf(x), BLACK); // 将父节点和父节点的兄弟节点置为黑色
                setColor(y, BLACK);
                setColor(parentOf(parentOf(x)), RED); // 将祖父节点置为红色,仍然满足黑路径平衡条件,且x和父节点的颜色不再冲突
                x = parentOf(parentOf(x)); // x替换为祖父节点
            } else { // 2. 如果y是黑色或者NIL节点,特别的,若y非空,则x并非新插入节点(x和兄弟节点在进入循环前都是黑色),是由于x的子树下存在新插入节点导致x变色为红色。
                if (x == rightOf(parentOf(x))) { //2.1 如果x是父节点的右子节点,说明父节点还有个左子节点是黑色
                    x = parentOf(x); // x替换为父节点
                    rotateLeft(x); // 对x左旋:x下沉一级,
                } //2.2 如果不满足if,则说明x是父节点的左子节点且x,同时父节点还有个右子节点是黑色
                setColor(parentOf(x), BLACK); // 将父节点置为黑色
                setColor(parentOf(parentOf(x)), RED); // 将祖父节点置为红色
                rotateRight(parentOf(parentOf(x))); // 对祖父节点右旋:祖父节点向右下沉一级,父节点上升一级,兄弟节点被转移到祖父节点的左子树上。
            }
        } else { // 与上面类似,获取叔叔节点的方式和旋转方向不一样
            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;
}

插入后调整的分支主要分两种情况:

1.叔叔节点是红色,只需将父节点和叔叔节点都染成黑色,将祖父节点染成红色,这样祖父节点以下的黑高维持不变,然后以祖父节点为参照继续往上递归调整。

 

2.叔叔节点为空或者黑色,这种情况就需要最多2次旋转来调整,而且旋转完因为局部根节点已经是黑色,所以会退出循环,调整结束。

2.1.当前节点N是父节点的右子节点,则选择N的父节点作为目标节点,进行左旋,然后并入步骤2.2

2.2.当前节点N是父节点的左子节点,则将父节点染成黑色,祖父节点染成红色,然后对祖父节点右旋。

 

需要注意的是,如果叔叔节点非空,则N并非新插入节点,而是在N的子树上有新插入节点,在调整过程中将N由黑变红了,所以在下图中的树只是整个树的局部展示,左右子树的黑高不相等,这也是上面提到要特别关注代码中标注2的原因。

 

 

 

参考:https://juejin.im/entry/58371f13a22b9d006882902d

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值