TreeMap源码解析

1、TreeMap类的说明

TreeMap是一种底层采用红黑树结构的哈希存储,数据内部保持有序,这个类继承了abstractMap和实现了NavigableMap。而NavigableMap又是拓展了SortedMap,具有了针对给定搜索目标返回最接近匹配项的导航方法,方法 lowerEntry、floorEntry、ceilingEntry 和 higherEntry 分别返回与小于、小于等于、大于等于、大于给定键的键关联的 Map.Entry 对象,如果不存在这样的键,则返回 null。类似地,方法 lowerKey、floorKey、ceilingKey 和 higherKey 只返回关联的键。所有这些方法是为查找条目而不是遍历条目而设计的。

1.1 解释说明

/**
 * 红黑树是基于NavigableMap实现的,这个map是通过它的键进行比较做自然排序的。
 * 在创建映射时提供的Comparator是取决于构造函数的。
 * 
 * 对于containsKey,get,put,remove等操作提供一个固定的log(n)的时间复杂度(因为底层是红黑树)
 * 这个算法是改编自Cormen, Leiserson, and Rivest这三位大佬的算法导论。
 * 
 * 注意如果要正确实现map接口,树形map维护的顺序(与任何排序map一样),以及是否提
 * 供显著的比较器,都必须与equals()保持一致。参阅comparable和comparator,以获得
 * 于equals()一致的精确定义,这是因为Map接口在equals上操作定义的,但是排序map执行
 * 的所有key比较是使用它的compareTo()或者compare()方法的,所以从排序Map角度两个key
 * 被视为相等是通过compareTo()或者compare()来比较。排序map的这种行为是良好的即使
 * 它的排序顺序于equals()不一致,它只是没有遵守Map接口的通用规则.
 * 
 * 
 * 注意这个方法不是一个同步的方法(这个跟前面的那些HashMap是一样的。),如果有多个线程同时
 * 结构性修改Map,应该自己加同步方法,这个synchronized通常在对象头中,如果不存在则应该用
 * 同步方法包装这个map
 *	SortedMap m = Collections.synchronizedSortedMap(new TreeMap(...));
 * 
 * 还要fail-fast的快速失败方法用来当有多个线程同时对map进行修改时候抛出异常,但是不能用这
 * 个机制来保证线程安全,这个只是用来检测bug用的。(这些都跟前面的hashMap差不多的机制,可以
 * 参考前面的hashMap的文章)
 * 
 * 所有Map.Entry这个类方法中返回的条目以及这个的视图的快照是不支持Entry.setValue,但是可以通过更改
 * 映射的put()方法来执行。
 */

1.2 数据结构

在这里插入图片描述
TreeMap底层就是一颗红黑树结构。符合红黑树的特性。

2、方法字段

2.1 一般方法

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{
    /**
     * 比较器用于维护TreeMap的键值映射,如果采用的是自然排序则这个
     * 比较器为空
     *
     * @serial
     */
    private final Comparator<? super K> comparator;
	//根节点
    private transient Entry<K,V> root;

    /**
     * 树中的节点个数
     */
    private transient int size = 0;

    /**
     * 对这个树做了多少次结构性修改
     */
    private transient int modCount = 0;

TreeMap的字段没有多少,comparator字段是用来使用自定义的比较排序规则的,我们可以自己定义一个红黑树的节点比较规则。

3、主要方法

构造函数


    /**
     * 构造一个新的,空的treeMap,默认使用自然排序,所有key的插入必实现Comparable
     * 接口。
	*/
    public TreeMap() {
        comparator = null;
    }
	/**
	 *
	 * 构造一个新的,空的树映射,根据给定的比较器排序       
     */
    public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }

    /**
     * 构造了一个包含相同映射的新的树映射,根据它的键的自然顺序排列。
     */
    public TreeMap(Map<? extends K, ? extends V> m) {
        comparator = null;
        putAll(m);
    }

    /**
     * 构造一个包含相同映射的新树映射,并使用指定的排序映射使用相同的排序。
     */
    public TreeMap(SortedMap<K, ? extends V> m) {
        comparator = m.comparator();
        try {
            buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
        } catch (java.io.IOException cannotHappen) {
        } catch (ClassNotFoundException cannotHappen) {
        }
    }

主要有四种,分为两大类型就是采用默认的比较规则和采用自己定义的比较规则,这里说一下Comparator和Comparable,Comparable是内部实现的,当一个类实现了这个借口,这个类的对象就能进行排序。而Comparator是外部实现的,可以通过重写compar方法对排序的规则进行重新定义,比如在Collections.sort()方法中就可以自己重写这个方法。

一些方法整合


    /**
     * 返回元素个数
     *
     * @return the number of key-value mappings in this map
     */
    public int size() {
        return size;
    }

    /**
     * 如果包含指定的key则返回true
     */
    public boolean containsKey(Object key) {
        return getEntry(key) != null;
    }

    /**
     * 如果包含一个或者多个value,则返回true,否则返回false
     */
    public boolean containsValue(Object value) {
        for (Entry<K,V> e = getFirstEntry(); e != null; e = successor(e))//从第一个entry开始比较
            if (valEquals(value, e.value))//如果value相等,则返回true
                return true;
        return false;//返回false
    }

    /**
     * 返回指定的key所映射的value或者,空如果不包含该键值对
     */
    public V get(Object key) {
        Entry<K,V> p = getEntry(key);
        return (p==null ? null : p.value);
    }
        /**
     *返回第一个key
     */
    public K firstKey() {
        return key(getFirstEntry());
    }

    /**
     * 返回最后一个key
     */
    public K lastKey() {
        return key(getLastEntry());
    }

lowerEntry、floorEntry、ceilingEntry 和 higherEntry 的具体实现


	/**
	* NavigableMap扩展了 SortedMap,具有了针对给定搜索目标返回最接近匹配项的导航方法。
	* 方法 lowerEntry、floorEntry、ceilingEntry 和 higherEntry 分别返回与小于、小于等于、
	* 大于等于、大于给定键的键关联的 Map.Entry 对象,如果不存在这样的键,则返回 null。类似地,
	* 方法 lowerKey、floorKey、ceilingKey 和 higherKey 只返回关联的键。所有这些方法是为查找条目
	* 而不是遍历条目而设计的。
	*/
    /**
     * 返回大于等于最接近key的节点
     */
    final Entry<K,V> getCeilingEntry(K key) {
        Entry<K,V> p = root;
        while (p != null) {//根节点不为空
            int cmp = compare(key, p.key);//比较
            if (cmp < 0) {//查找过程,小于,往左找
                if (p.left != null)
                    p = p.left;
                else
                    return p;//找不到节点,但是来到最接近
            } else if (cmp > 0) {//大於则往右找
                if (p.right != null) {
                    p = p.right;
                } else {//如果不存在右节点,则应该返回到当前节点的父节点的右节点
                    Entry<K,V> parent = p.parent;
                    Entry<K,V> ch = p;
                    while (parent != null && ch == parent.right) {
                        ch = parent;
                        parent = parent.parent;
                    }
                    return parent;//找不到节点,来到最接近
                }
            } else
                return p;//找到于key相等的节点,直接返回
        }
        return null;
    }

    /**
     * 找到小于等于最接近key的值,整个过程跟上面的差不多,是一个相反的过程而已
     */
    final Entry<K,V> getFloorEntry(K key) {
        Entry<K,V> p = root;
        while (p != null) {
            int cmp = compare(key, p.key);
            if (cmp > 0) {
                if (p.right != null)
                    p = p.right;
                else
                    return p;
            } else if (cmp < 0) {
                if (p.left != null) {
                    p = p.left;
                } else {
                    Entry<K,V> parent = p.parent;
                    Entry<K,V> ch = p;
                    while (parent != null && ch == parent.left) {
                        ch = parent;
                        parent = parent.parent;
                    }
                    return parent;
                }
            } else
                return p;

        }
        return null;
    }

    /**
     * 找到大于key的节点,没有等于操作,
     */
    final Entry<K,V> getHigherEntry(K key) {
        Entry<K,V> p = root;
        while (p != null) {
            int cmp = compare(key, p.key);
            if (cmp < 0) {
                if (p.left != null)
                    p = p.left;
                else
                    return p;
            } else {
                if (p.right != null) {
                    p = p.right;
                } else {
                    Entry<K,V> parent = p.parent;
                    Entry<K,V> ch = p;
                    while (parent != null && ch == parent.right) {
                        ch = parent;
                        parent = parent.parent;
                    }
                    return parent;
                }
            }
        }
        return null;
    }

    /**
     * 找到小于key的节点,没有等于操作
     */
    final Entry<K,V> getLowerEntry(K key) {
        Entry<K,V> p = root;
        while (p != null) {
            int cmp = compare(key, p.key);
            if (cmp > 0) {
                if (p.right != null)
                    p = p.right;
                else
                    return p;
            } else {
                if (p.left != null) {
                    p = p.left;
                } else {
                    Entry<K,V> parent = p.parent;
                    Entry<K,V> ch = p;
                    while (parent != null && ch == parent.left) {
                        ch = parent;
                        parent = parent.parent;
                    }
                    return parent;
                }
            }
        }
        return null;
    }

具体可以看到这四个方法基本都是按照二叉排序树的方式进行查找到相应的元素

2.2 重要方法

TreeMap最重要的两个方法就是put()remove(),因为这两个方法涉及到对红黑树的增加删除操作,而且我们知道红黑树复杂就在于它的添加节点和删除几点操作,所以在介绍着两个方法之前,我们先来介绍一下,什么是红黑树。

一、概念

R-B Tree,全称是Red-Black Tree又称红黑树,它是一种特殊的二叉查找树,红黑树的每个节点上都有存储位表示节点的颜色,可以是红或黑。

二、特性

1、每个节点或者是红色,或者是黑色

2、根节点是黑色的

3、每个叶子节点(NIL)是黑色的。注意:这里的叶子节点,是指为空的叶子节点

4、如果一个节点是红色的,则它的子节点必须是黑色的

5、从任意一个节点到其叶子的所有路径中,所包含的黑节点数量是相同的

特性解析1:根据特性4可知,从每个叶子节点到根节点的所有路径中不能有两个连续的红节点

特性解析2:根据特性5可知,没有一条路径会比其它路径长出两倍,因而红黑树是接近平衡的二叉树

:红黑树相比较于平衡二叉树没有那么严格的要求,它对节点的平衡操作不会比平衡二叉树更加频繁,所以红黑树经常用在插入删除比较多的情况下,而平衡二叉树用在查找比较多的情形下。

三、插入删除操作

通过上面的介绍我们知道当插入一个节点或者删除一个节点的时候,会导致5这个条件不成立。所以就要对红黑树进行调整,而调整主要有两部分:
1、变色
2、旋转
<2.1>旋转主要有左旋和右旋
左旋:对节点x进行左旋,意味着将“x的右孩子变成x的父亲”,而将“x原先的右孩子的左孩子变成x的右孩子”。
即左旋中的“左”是指将别旋转的节点变成一个左节点

treeMap中左旋源码

    /** 左旋操作
	*	对节点x进行左旋,意味着将“x的右孩子变成x的父亲”,
	*	而将“x原先的右孩子的左孩子变成x的右孩子”。
	*	即左旋中的“左”是指将别旋转的节点变成一个左节点
	*/
    private void rotateLeft(Entry<K,V> p) {
        if (p != null) {
            Entry<K,V> r = p.right;//p的右节点
            p.right = r.left;//将r的左节点作为p的右节点
            if (r.left != null)//如果r存在左节点
                r.left.parent = p;//既将r的左节点链接到p的右节点
            r.parent = p.parent;//p作为r的左节点
            if (p.parent == null)//p为根节点
                root = r;//r为根节点
            else if (p.parent.left == p)//p存在父节点
                p.parent.left = r;//将r作为p的父节点的左节点
            else
                p.parent.right = r;//p是父节点的右节点
            r.left = p;//r的左节点为p
            p.parent = r;//p的父节点为r
        }
    }

右旋:对节点x进行右旋,意味着将“x的左孩子变成x的父亲,而将”x原先的左孩子的右孩子变成x的左孩子“。
即右旋中的”右“是指将被旋转的节点变成一个右节点

treeMap中右旋源码

    /** 右旋操作
	*
	* 	对节点x进行右旋,意味着将“x的左孩子变成x的父亲,
	*   而将”x原先的左孩子的右孩子变成x的左孩子“。
	*	即右旋中的”右“是指将被旋转的节点变成一个右节点
	*/
    private void rotateRight(Entry<K,V> p) {
        if (p != null) {//p不为空
            Entry<K,V> l = p.left;//p的左节点
            p.left = l.right;//将p的左节点置为l的右节点
            if (l.right != null) l.right.parent = p;//p的左节点为l的右节点关联起来
            l.parent = p.parent; //l替换了原先的p的位置
            if (p.parent == null)//p没有根节点了
                root = l;//则l就是根节点
            else if (p.parent.right == p)//如果p是父节点的右节点
                p.parent.right = l;//则l变成p的父节点的右节点,
            else p.parent.left = l;//p为父节点的左节点
            l.right = p;//将父节点连接到它的左节点的右节点
            p.parent = l;//p和l的换位置过程
        }
    }

而在插入和删除过程中主要有两种旋转和一种变色

图1:两种旋转(第一个右旋第二个左旋,可以参照着源码自己模拟一下)

在这里插入图片描述
图2:一种变色

在这里插入图片描述

有了这几个基础之后,我们来开始看一下红黑树是怎么样对插入和删除进行调整的。

1、插入

(情形一)新节点位于根节点,其没有父节点时,处理思路:将该节点直接设为黑色即可

(情形二)新节点的父节点已然是黑色时,处理思路:不用动,这已然是一颗红黑树

(情形三)父节点和叔节点都是红色时,处理思路:a.将父节点和叔节点设为黑色;b.将祖父节点设为红色;c.将祖父节点设为当前节点,并继续对新当前节点进行操作

(情形四)父节点是红色,叔节点是黑色时,又分如下四种情况:
① :当前节点是父亲的左孩子,父亲是祖父的左孩子(Left-Left),处理思路:a.将祖父节点右旋;b.交换父节点和祖父节点的颜色
②:当前节点是父亲的右孩子,父亲是祖父的左孩子(Right-Left),处理思路:a.将父节点左旋,并将父节点作为当前节点; b.然后再使用Left Left情形
③:当前节点是父亲的右孩子,父亲是祖父的右孩子(Right-Right),处理思路:a.将祖父节点左旋;b.交换父节点和祖父节点的颜色
④:当前节点是父亲的左孩子,父亲是祖父的右孩子(Left-Right),处理思路:a.将父节点右旋,并将父节点作为当前节点; b.然后再使用Right Right情形

然后我们来看一下TreeMap里面的源码是怎么做的

put()方法

 public V put(K key, V value) {
        Entry<K,V> t = root;
        if (t == null) {
            compare(key, key); // 检查是否为空

            root = new Entry<>(key, value, null);//空节点
            size = 1;//说明一个空key的长度也为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;
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);//如果存在相同的key,则替换
            } 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);
        }
        Entry<K,V> e = new Entry<>(key, value, parent);//不存在则创建一个新的Entry进行插入操作
        if (cmp < 0)
            parent.left = e; //判断是插在父节点的左侧还是右侧
        else
            parent.right = e;
        fixAfterInsertion(e);//插入元素之后的修正
        size++;//节点加1
        modCount++;//结构性修改
        return null;
    }

put()方法主要是找到插入的位置,主要是调用了fixAfterInsertion(),我们来看看这个方法是怎么做的。


    /**
	*
	*红黑树的插入操作,新插入的节点都为红色节点
	*/
    private void fixAfterInsertion(Entry<K,V> x) {
        x.color = RED;

        while (x != null && x != root && x.parent.color == RED) {//判断x不为空,x不为根节点,x的父节点为红色(如果是黑色就不必调整了)
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {//如果x的父节点是x的祖父节点的左节点
                Entry<K,V> y = rightOf(parentOf(parentOf(x)));//y是x祖父的右节点
                if (colorOf(y) == RED) {//如果y的颜色是红色(也就是x的父节点和x的父节点的兄弟节点是红色的,
                						//这是第一种情况调整,直接将这两个变黑,且将祖父节点变红即可)(---------->情形三)
                    setColor(parentOf(x), BLACK);//x父节点变黑
                    setColor(y, BLACK);//x的父节点的兄弟节点变黑
                    setColor(parentOf(parentOf(x)), RED);//x的祖节点变红
                    x = parentOf(parentOf(x));//x来到祖先节点,继续判断是否这一层需要改变
                } else {//x的祖父的右节点是黑色的(--------------->情形四)
                    if (x == rightOf(parentOf(x))) {
                        x = parentOf(x);//x变成其父节点
                        rotateLeft(x);//左旋
                    }
                    setColor(parentOf(x), BLACK);//将x的父节点置为黑色(------------------->情形二)
                    setColor(parentOf(parentOf(x)), RED);//x的祖先节点置为红色
                    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;//如果最后操作来到根节点,将根节点变黑(--------------->情形一)
    }

可能这样子看有点抽象,我们用一个栗子来说明它整个过程是怎么样做的。

通过插入12 1 9 2 0 11 7 19 4 15 18 5 14 13 10 16 6 3 8 17完成上述所有情形的展示。
(1)插入12
在这里插入图片描述

说明:插入的节点若是根节点,则直接将其设置为黑色

(2)插入1
在这里插入图片描述

(3)插入9

在这里插入图片描述
(4)插入2
在这里插入图片描述
(5)插入0
在这里插入图片描述
(6)插入11
在这里插入图片描述
(7)插入7
在这里插入图片描述
(8)插入19
在这里插入图片描述
(9)插入4
在这里插入图片描述
(10)插入15
在这里插入图片描述
(11)插入18
在这里插入图片描述
(12)插入5
在这里插入图片描述
(13)插入14
在这里插入图片描述
(14)插入13
在这里插入图片描述
(15)插入10
在这里插入图片描述
(16)插入16
在这里插入图片描述
(17)插入6
在这里插入图片描述
(18)插入3
在这里插入图片描述
(19)插入8
在这里插入图片描述
(20)插入17
在这里插入图片描述
插入完成。

3.2 删除操作

remove()

  public V remove(Object key) {
        Entry<K,V> p = getEntry(key);
        if (p == null)
            return null;

        V oldValue = p.value;
        deleteEntry(p);
        return oldValue;
    }

可以看到remove()主要是调用了deleteEntry的方法,那么它是怎么实现的呢。
相比较于插入操作,删除操作比较复杂,还要判断删除节点的孩子,兄弟,父亲的节点状态。下面我们来看一下.

删除一个节点一般分为以下两个部分,首先是判断要删除节点类型,将复杂类型转为简单类型。然后开始旋转调色。

一、首先是找到要删节点,判断其孩子类型

情况①:如果X没有孩子,且如果X是红色,直接删除X;如果X是黑色,则以X为当前节点进行旋转调色,最后删掉X

情况②:如果X只有一个孩子C,交换X和C的数值,再对新X进行删除。根据红黑树特性,此时X不可能为红色,因为红色节点要么没有孩子,要么有两个黑孩子。此时以新X为当前节点进行情况①的判断

情况③:如果X有两个孩子,则从后继中找到最小节点D,交换X和D的数值,再对新X进行删除。此时以新X为当前节点进行情况①或②的判断

treeMap源码

 /**
     * 删除一个节点,并且调整
     */
	 
	 //删除的节点存在三种情况:
	 /*
	 *	1、被删节点没有孩子节点
	 *	2、被删节点只有一个孩子节点
	 *  3、被删节点有左右孩子节点
	 *
	 */
    private void deleteEntry(Entry<K,V> p) {
        modCount++;//结构性修改
        size--;//元素个数减1

        // 如果被删除节点存在左右节点, 找到p中继遍历的后续节点
		
        //这个过程就是将第3转为第1或者第2种情况
        if (p.left != null && p.right != null) {//(------------------------>情形3)
            Entry<K,V> s = successor(p);//找到p的后继节点
			//这三步是将p节点的值改为s节点,但是颜色并没有改变
            p.key = s.key;
            p.value = s.value;
            p = s;//将p指向s所指节点
        } 

        // 开始调整
        Entry<K,V> replacement = (p.left != null ? p.left : p.right);//判断是否存在左节点,存在则指向左节点,不存在指向右节点

        if (replacement != null) {//存在左节点或者右节点(-------------------->情形2)
            
            replacement.parent = p.parent;//将replacement节点作为p的父节点
			//这三步就是用来将p替换掉为它的孩子节点
            if (p.parent == null)//p本身就是根节点
                root = replacement;//将根节点置为replacement
            else if (p == p.parent.left)//p是其父节点的左节点
                p.parent.left  = replacement;//用p的孩子节点替换掉p
            else//p是其父节点的右节点,这种情形会存在吗????
                p.parent.right = replacement;//也是一样

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

            // Fix replacement
            if (p.color == BLACK)//如果p是黑色节点,因为删除黑色节点后在某条路径上黑色节点就不平衡,调整
                fixAfterDeletion(replacement);//删除replacement,因为前面的已经处理好了
        } else if (p.parent == null) { // 仅有一个节点,则删除了,就没了
            root = null;
        } else { //  没有孩子节点. 
            if (p.color == BLACK)//p是黑色节点(---------------------------->情形1)
                fixAfterDeletion(p);//删除后的修正

            if (p.parent != null) {//p的父节点不为空
                if (p == p.parent.left)//p是父节点的左节点
                    p.parent.left = null;//删除
                else if (p == p.parent.right)//p是父节点的右节点
                    p.parent.right = null;//删除
                p.parent = null;
            }
        }
    }

二、旋转调色(N=旋转调色的当前节点[等于情况①中的X],P=N的父亲,W=N的兄弟,Nf=N的远侄子,Nn=N的近侄子)

情况1N是根或者N是红色,则:直接将N设为黑色

情况2N不是根且N是黑色,且W为红色,则:将W设为黑色,P设为红色,对P进行旋转(N为P的左子时进行左旋,N为P的右子时进行右旋),将情况转化为情况1、2、3、4、5

情况3N不是根且N是黑色,且W为黑色,且W的左右子均为黑色,则:将W设为红色,将P设为当前节点进行旋转调色,将情况转化为情况1、2、3、4、5

情况4N不是根且N是黑色,且W为黑色,且Nf为黑色,Nn为红色,则:交换W与Nn的颜色,并对W进行旋转(N为P的左子进行右旋,N为P的右子进行左旋),旋转后N的新兄弟W有一个红色WR,则转换为情况5

情况5N不是根且N是黑色,且W为黑色,且Nf为红色,Nn为黑色,则:将W设为P的颜色,P和Nf设为黑色,并对P进行旋转(N为P的左子进行左旋,N为P的右子进行右旋),N设为根

TreeMap源码

  /** 
	*
	* 红黑树删除节点后的调整操作(旋转调色)
	*/
    private void fixAfterDeletion(Entry<K,V> x) {
		//直到x不是跟节点且x是黑色节点为止
        while (x != root && colorOf(x) == BLACK) {
            if (x == leftOf(parentOf(x))){//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) {//sib的左右节点均是黑色的(--------------------->情况五)
                    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 { // 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);//根节点为黑色(---------------->情况一)
    }

我们还是用上面那个栗子,对元素进行删除。
通过删除12 1 9 2 0 11 7 19 4 15 18 5 14 13 10 16 6 3 8 17完成上述所有情形的展示。

(1)删除12
在这里插入图片描述

(2)删除1
在这里插入图片描述
(3)删除9
在这里插入图片描述
(4)删除2
在这里插入图片描述
(5)删除0
在这里插入图片描述
(6)删除11
在这里插入图片描述
(7)删除7
在这里插入图片描述
(8)删除19
在这里插入图片描述
(9)删除4
在这里插入图片描述
(10)删除15
在这里插入图片描述
(11)删除18
在这里插入图片描述
(12)删除5
在这里插入图片描述
(13)删除14
在这里插入图片描述
(14)删除13
在这里插入图片描述
(15)删除10
在这里插入图片描述
(16)删除16
在这里插入图片描述
(17)删除6
在这里插入图片描述
(18)删除3
在这里插入图片描述
(19)删除8
在这里插入图片描述
(20)删除20
在这里插入图片描述
删除完成。

至此,put()方法和remove()方法都解析完毕,通过源码分析我们可以发现,
(1)对于put的方法主要是找到要插入的位置,插入的节点必定为红色,且在相对于没有NIL的根节点上,然后调用fixAfterInsertion()方法进行调整,调整的过程主要用四种情形。
(2)对于remove方法,主要是要先判断被删除节点的孩子节点是否存在,有多少个,如果是左右孩子都存在的,则将其转换为第1,2种情形。然后再进行调整删除。

4、总结

通过TreeMap的源码我们可以发现,它的主要方法有一个是针对给定搜索目标返回最接近匹配项的导航方法,方法 lowerEntry、floorEntry、ceilingEntry 和 higherEntry 。另外就是插入和删除操作,这个涉及到红黑树的操作,而红黑树的操作理解起来也是很难,我通过例子和源码的解析,希望能更好的理解红黑树。其中主要参考别人的解析,对红黑树的理解程度还不够深刻,其中难免有错误,还望指正。
此外TreeSet中的结构跟TreeMap是一样的,只不过TreeSet在插入的过程中判断了是否存在相同的Key,存在则不插入,不存在则插入

参考资料:
https://blog.csdn.net/u010126792/article/details/62236367 Java集合之NavigableMap与NavigableSet接口
https://my.oschina.net/u/3272058/blog/1914452 红黑树原理以及插入、删除算法 附图例说明
https://www.cnblogs.com/finite/p/8251587.html HashMap分析之红黑树树化过程

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章