TreeMap核心方法源码分析--基于红黑树的算法实现

我们知道,TreeMap是一个基于key有序的二叉查找树,其底层实现的原理是一颗红黑树。本文从源码的角度,分析TreeMap的最重要的几个方法,来对红黑树的原理作出讲解。

红黑树的特性:

1.所有节点不是红色就是黑色
2.红色节点后面必须是黑色节点
3.根节点必须是黑色节点
4.叶子节点必须是黑色节点
5.任意一个节点到叶子节点的黑色节点必须相同

左旋方法

TreeMap核心方法源码分析--基于红黑树的算法实现_第1张图片

    private void rotateLeft(Entry p) {
        if (p != null) {    //中心节点不为空才进行左旋,p为上图的X节点
            Entry r = p.right;   // 将Y节点赋给r
            p.right = r.left;  //将β赋给X的左子树
            if (r.left != null)//如果β不为空
                r.left.parent = p;  //将X设为β的父
            r.parent = p.parent;  //将X的父亲赋给Y
            if (p.parent == null)  //如果X的父亲为空
                root = r;      //将Y设置为新的根节点
            else if (p.parent.left == p)  //如果X是其父的左子树
                p.parent.left = r;    //将其父的左子树设置为Y
            else
                p.parent.right = r;  //否则,将其父的又子树设置为Y
            r.left = p;  // 将X设置为Y的左子树
            p.parent = r; //将X的父设置为Y
        }
    }

右旋方法

TreeMap核心方法源码分析--基于红黑树的算法实现_第2张图片

    private void rotateRight(Entry p) {
        if (p != null) {  //如果Y节点不为空节点,进行右旋转
            Entry l = p.left;  //用l引用X
            p.left = l.right;    //将β设置为Y的左子树
            if (l.right != null) 
                l.right.parent = p;  //如果β不为空,将β的父设置为Y
            l.parent = p.parent;    //将X的父赋给Y的父
            if (p.parent == null)  //如果Y的父为空
                root = l;    //将X设为树的根
            else if (p.parent.right == p) //如果Y是父的的右子树
                p.parent.right = l;  //将Y的父的右子树设置为X
            else p.parent.left = l; //负责Y的父的左子树设置为X
            l.right = p;  //X的右子树设置为Y
            p.parent = l;  //Y的父设置为X
        }
    }

put方法

public V put(K key, V value) {
        Entry t = root;  //
        if (t == null) {
            compare(key, key); // 无意义的比较,防止空指针异常

            root = new Entry<>(key, value, null);  //搞一个Entry出来
            size = 1;  //树的大小为1
            modCount++;  //树被修改的次数设置为1
            return null;   //返回空,树中无此条目,所以也无旧值
        }
        int cmp;  //比较当前节点和key的大小的临时变量
        Entry parent;  //当前节点的父应用
        // split comparator and comparable paths
        Comparator cpr = comparator;
        if (cpr != null) {  //如果比较器不为空,用比较器比较
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);  //当前节点和key比较
                if (cmp < 0)  //小于0,走左子树
                    t = t.left;
                else if (cmp > 0)  //大于0,走右子树
                    t = t.right;
                else  //如果等于0,说明找到目标条目,新值替换旧值,旧值返回
                    return t.setValue(value);
            } while (t != null);  //一直比较直到t为空,说明没找到目标节点,说明要进行插入操作
        }
        else {  //如果没有比较器,说明自身可比较,大致流程和上面相同
            if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
                Comparable k = (Comparable) 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 e = new Entry<>(key, value, parent);  //生成一个新的结点,父就是最后跟踪到的非叶子节点
        if (cmp < 0) //如果小于0,说明最后一次比较,走到父的左孩子上去了
            parent.left = e; //将父的左孩子设置为e
        else
            parent.right = e;  //将父的右孩子设置为e
        fixAfterInsertion(e);  //修复树,因为新增接待你会破坏红黑树的特性(重点)
        size++;  //大小加1
        modCount++;  //修改次数加1
        return null;  //新增节点,没有旧值,返回null
    }
   //红黑树算法的重点,下面不要害怕,会图文分解讲解
    private void fixAfterInsertion(Entry x) {
        x.color = RED;  //所有新增加的节点,着红色,因为红色不会为这条路径增加黑色节点,破坏特性5
        
        // 通过变色和旋转,使得红黑树重新复合5条特性,从新增加的节点开始调整
        // 当x为根,x为空或者x的父为黑,停止调整
        while (x != null && x != root && x.parent.color == RED) {
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {  //x的父为x爷爷的左子树
                Entry y = rightOf(parentOf(parentOf(x)));  //y为x的叔叔
                //如果y也为红色,说明他爸和他叔都为红色,此时他爷爷肯定为黑色
                //此时让他爸爸叔叔和他爷爷换一下颜色,即可局部搞定
                // 情况1
                if (colorOf(y) == RED) { 
                    setColor(parentOf(x), BLACK);  //让他爸变黑
                    setColor(y, BLACK);  //让他叔叔变黑
                    setColor(parentOf(parentOf(x)), RED);  //让他爷爷变红
                    //将爷爷作为当前节点,继续调整
                    //因为他爷爷现在是红色,而他爷爷的爸也可能是红色,所以要继续调整
                    x = parentOf(parentOf(x));   
                } else {
                    //此时,他爸为红,他叔叔为黑色,不好搞,只能通过旋转
                    // 如果我为我爸的右子树,此时需要我爸左旋,等待下一步处理
                    //旋转后,我爸变成了我,我变成了我爸,原来的爸成为当前处理节点
                    if (x == rightOf(parentOf(x))) {  //情况2
                        x = parentOf(x);  //情况2
                        rotateLeft(x);  //情况2
                    }
                    setColor(parentOf(x), BLACK);   //爸爸变成黑的 情况3
                    setColor(parentOf(parentOf(x)), RED); //将爷爷变为红的 情况3
                    rotateRight(parentOf(parentOf(x)));  // 右旋搞定  情况3
                }
            } else {
            // 他爸是爷爷的右边节点,处理情况类似上面
                Entry 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;  //将根变为黑色,防止根被调整为红色退出调整
    }

A. 情况1
TreeMap核心方法源码分析--基于红黑树的算法实现_第3张图片
B. 情况2
TreeMap核心方法源码分析--基于红黑树的算法实现_第4张图片
C. 情况3
TreeMap核心方法源码分析--基于红黑树的算法实现_第5张图片

delete方法

节点的删除肯定不是叶子节点,当删除的节点本身是黑色需要进行调整,当删除的节点是红色时,无需对红黑树进行调整。

 private void deleteEntry(Entry p) {
        modCount++;    //节点的修改次数加1
        size--;    //树的节点减1

        //当左子树右子树都不为空时,需要找到P的后继节点来与P交换
        //同时删除P的后继节点
        //p的后继节点应该是P最右边的子孙
        if (p.left != null && p.right != null) {  
            Entry s = successor(p);
            p.key = s.key;
            p.value = s.value;
            p = s;
        } 

        //找到要删除的节点,此时该节点要么左子树为空,要么又子树为空,或者全空
        //找到不为空的子树来顶替P的位置
        Entry replacement = (p.left != null ? p.left : p.right);
     
       //当替代者不为空时,要用替代者顶替P
        if (replacement != null) {
            // p的父赋值给替代者的父
            replacement.parent = p.parent;
            if (p.parent == null)
                root = replacement; //如果P的父为空,那么将替代者设为根节点
            else if (p == p.parent.left)
                p.parent.left  = replacement;  //P为父的左,将替代者设置为父的左
            else
                p.parent.right = replacement;  //P为父的右,将替代者设置为父的右

            // Null out links so they are OK to use by fixAfterDeletion.
            p.left = p.right = p.parent = null;  //释放P

            // 如果被删的P是黑色,那么需要修复树,
            // 目标是来弥补p的缺失让通往叶子节点路径的黑色节点数量少一的损失
            if (p.color == BLACK)
                fixAfterDeletion(replacement);
        } else if (p.parent == null) { 
           // 如果替代者为空,说明无左右孩子,且为根,说明树就这么一个节点
            root = null;   //将树设为空
        } else { //  无孩子不为根
            if (p.color == BLACK)  //自身是黑色,也需要通过修复补偿黑节点丢失的损失
                fixAfterDeletion(p);

            if (p.parent != null) {  //自身为红,将父母的指向设为空就ok
                if (p == p.parent.left)
                    p.parent.left = null;
                else if (p == p.parent.right)
                    p.parent.right = null;
                p.parent = null;  //自身对父母的指针也设为空
            }
        }
    }

    /** From CLR */
    private void fixAfterDeletion(Entry x) {
       //调整结束的标准是x为根节点或者x本身为红色
        while (x != root && colorOf(x) == BLACK) {
            if (x == leftOf(parentOf(x)))  {  //如果x是父的左孩子,以此为例解释,右边对称处理
                Entry sib = rightOf(parentOf(x));  //sib是兄弟节点

                if (colorOf(sib) == RED) {  // 如果兄弟节点是红色,下图情况1
                    setColor(sib, BLACK);  // 将兄弟节点变黑
                    setColor(parentOf(x), RED);  //将父节点变红
                    rotateLeft(parentOf(x));  //以父节点为中心左旋
                    sib = rightOf(parentOf(x));  //从新获取新的兄弟节点
                }

                if (colorOf(leftOf(sib))  == BLACK &&
                    colorOf(rightOf(sib)) == BLACK) {  //如果左右侄子都为黑,情况2
                    setColor(sib, RED);  //将兄弟设置为红
                    x = parentOf(x);  //将x的父变成新的x
                } else {
                    if (colorOf(rightOf(sib)) == BLACK) { //如果左侄子为红,右侄子为黑,情况3
                        setColor(leftOf(sib), BLACK); // 左侄子变黑
                        setColor(sib, RED);  //兄弟变红
                        rotateRight(sib);  //以兄弟为核心,右旋
                        sib = rightOf(parentOf(x));   //左侄子变为兄弟
                    }
                    //此时左侄子为黑,又侄子为红,情况4	
                    setColor(sib, colorOf(parentOf(x)));  // 将父的颜色给兄弟
                    setColor(parentOf(x), BLACK);  //将父设为黑色
                    setColor(rightOf(sib), BLACK); //将右侄子设为黑色
                    rotateLeft(parentOf(x));  // 以父为核心左旋
                    x = root;  //让根赋值给x,退出循环
                }
            } else { // symmetric
                Entry 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;
                }
            }
        }
        //将x设置为黑色,防止调整到最后,根为红色,情况1可能导致这种情况
        setColor(x, BLACK); 
    }

情况1

TreeMap核心方法源码分析--基于红黑树的算法实现_第6张图片

情况2

TreeMap核心方法源码分析--基于红黑树的算法实现_第7张图片

情况3

TreeMap核心方法源码分析--基于红黑树的算法实现_第8张图片

情况4

TreeMap核心方法源码分析--基于红黑树的算法实现_第9张图片

你可能感兴趣的:(Java基础)