Java集合-TreeMap和红黑树

TreeMap是一种通过实现了红黑树数据结构的Map集合。

【图片有英文注释的均摘抄于国外文章】

首先,先来看一些基础概念。

1. 二叉排序树

二叉排序树的定义和性质:
(1)若左子树不空,则左子树上所有结点的值均小于或等于它的根结点的值;
(2)若右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值;
(3)左、右子树也分别为二叉排序树;
【摘自百度百科】
.
如下图是一个普通的二叉树结构:

《Java集合-TreeMap和红黑树》 二叉树【摘抄于国外文章】

上图是相应的根据值比较生成的一个简单的二叉树,那么对应查找就非常简单,不断通过比较节点的值,大于就向右子树走,小于就往左子树走,相同则返回当前值.

《Java集合-TreeMap和红黑树》 二叉树查找【摘抄于国外文章】

对应插入就可以类似查找,先查到当前值于节点比较,如果找到,直接更新,没有找到就不断往子节点寻找,直到到达为空的子节点,将值填入到该空节点上。

《Java集合-TreeMap和红黑树》 二叉树插入【摘抄于国外文章】

二叉树查找的时候,查找的效率和树的形状有关,当节点完全平衡时(最底层子节点到根节点的“路程”相同),效率最高;当所有节点都只有一个子节点时,效率最差

《Java集合-TreeMap和红黑树》 二叉树效率【摘抄于国外文章】

2. B树

B-树是一种多路搜索树(并不一定是二叉的)
如下图,节点内有多个值(多路):

《Java集合-TreeMap和红黑树》 B树【摘抄于国外文章】

上图展示的最多只有两个三个子节点的B树,这种简单结构的树可以称为2-3树,这里点到为止,后续将通过这种树去分析后续的红黑树

3. 2-3树

2-3树允许每个节点保存1个或2个值,含有1个值节点称为2-node(有两个子节点),含有2个值的节点称为3-node(有三个子节点)。
如图:

《Java集合-TreeMap和红黑树》 2-3树【摘抄于国外文章】

在2-3树中查找,与二叉树类似,通过不断和节点比较进行向下查找,需要注意的是,在3-node节点中,需要同时比较两个值,如果介于两个值之间,需要找的子节点就是中间的子节点。

《Java集合-TreeMap和红黑树》 2-3树查找过程【摘抄于国外文章】

这里重点讨论一下插入操作,首先看一种简单的,往2-node节点插入;如果找到的末节点是一个2-node的节点,直接在此节点上新增当前值,将其节点变成3-node节点,如图:

《Java集合-TreeMap和红黑树》 2-3树2-node新增【摘抄于国外文章】

当插入的节点是3-node节点时,应该怎么操作?首先看一下一个最简单的,只有一个3-node节点的树:

《Java集合-TreeMap和红黑树》 2-3树3-node根新增【摘抄于国外文章】

首先,将值插入到当前的3-node节点,将其变成4-node节点,然后提取中间的值,让其分解为一个普通的二叉树即可。

那么如果当前插入的3-node节点有父节点时应该怎么处理呢,首先还是将3-node变成4-node节点,然后拆解出一个父节点插入到原来的父节点中,如果当前父节点是2-node节点,那么将其变成3-node节点,如果原来父节点已经是3-node节点,那么循环上面的处理步骤。

《Java集合-TreeMap和红黑树》 2-3树3-node根新增【摘抄于国外文章】
《Java集合-TreeMap和红黑树》 2-3树3-node根新增【摘抄于国外文章】

总结一下步骤:

  1. 查找需要插入值的位置
  2. 判断当前插入节点是否是2-node节点,如果是,则将其变成3-node节点,如不是,继续执行一下步骤
  3. 将当前节点变成4-node节点
    4.分解当前4-node节点,判断当前节点是否为根节点,如果是,将整个2-3树,提高一层,如不是,继续执行一下步骤
  4. 新增的上层节点插入到父节点中,重复执行2-4步,直到结束

4. 红黑树

红黑树是一种解决平衡二叉树的方法。红黑树具有以下性质:

  1. 节点是红色或黑色。
  2. 根节点是黑色。
  3. 每个叶节点(NIL节点,空节点)是黑色的。
  4. 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
    5.从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
    【摘自百度百科】

《Java集合-TreeMap和红黑树》 红黑树【摘抄于国外文章】

红黑树其实是2-3树的一种二叉树表现方法,如果将红线连接线水平连接,那么连接的两个节点就是2-3树中的3-node节点,其他没有红线连接的就是2-3树中的2-node节点,如图:

《Java集合-TreeMap和红黑树》 红黑树与2-3树【摘抄于国外文章】

在讲解插入操作前,先了解集中红黑树的平衡操作。第一种是左旋转和右旋转:

《Java集合-TreeMap和红黑树》 左旋转【摘抄于国外文章】
《Java集合-TreeMap和红黑树》 右旋转【摘抄于国外文章】

第二种是颜色反转,当出现一个节点下的两个节点均是红色时,可以转换到2-3树中思考,这是一个4-node节点,那么需要将其分解为一个二叉树,并向上合并:

《Java集合-TreeMap和红黑树》 颜色转换【摘抄于国外文章】

有了2-3树的插入讲解,这边,直接分析最复杂的红黑树插入:

《Java集合-TreeMap和红黑树》 红黑树插入【摘抄于国外文章】

  1. 查找需要插入值的位置
  2. 新插入的节点元素用红色标识(2-3树中插入到2-node节点,将其变成3-node节点)
  3. 如果出现节点下的两个子节点都是红色时(4-node节点),需要进行颜色转换(或旋转)

选取红黑树比完全平衡二叉树的好处就是,红黑树是一种代价较小就可以实现较平衡的二叉树的结构,最差的情况也就是比完全二叉树的深度多一倍,但是在删除操作的平衡里面可以节省很多的效率。

5. TreeMap

通过分析二叉树和红黑树的概念,再来看源码,首先看TreeMap的容器:

private transient Entry<K,V> root = null;

其中,TreeMap重写了Entry类:

K key;
V value;
Entry<K,V> left = null;
Entry<K,V> right = null;
Entry<K,V> parent;

其中key value是为了实现Map的键值结构,left、right、parent表示的是二叉树中一个节点与其他节点的关系指针。

TreeMap提供了节点值比较的接口:

private final Comparator<? super K> comparator;

接下来,看一下put方法:

public V put(K key, V value) {
    Entry<K,V> t = root;
    ... ...
    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);
        } while (t != null);
    }
    ... ...
    fixAfterInsertion(e);
    ... ...
}

首先获取当前的比较器,运用当前比较器作为后续比较的工具(比较器为空则用默认的,默认的逻辑和有比较器的类似,不重复分析).然后小于,则跳到左边的节点继续比较,如果大于则在右边子节点比较,如果相同,则设置当前值。然后调用fixAfterInsertion平衡红黑树操作。

先偷一下懒,里面的平衡源码就不分析了。

    原文作者:懒懒惰惰
    原文地址: https://www.jianshu.com/p/c109ffd0cc26
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞