TreeMap是一种通过实现了红黑树数据结构的Map集合。
【图片有英文注释的均摘抄于国外文章】
首先,先来看一些基础概念。
1. 二叉排序树
二叉排序树的定义和性质:
(1)若左子树不空,则左子树上所有结点的值均小于或等于它的根结点的值;
(2)若右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值;
(3)左、右子树也分别为二叉排序树;
【摘自百度百科】
.
如下图是一个普通的二叉树结构:
上图是相应的根据值比较生成的一个简单的二叉树,那么对应查找就非常简单,不断通过比较节点的值,大于就向右子树走,小于就往左子树走,相同则返回当前值.
对应插入就可以类似查找,先查到当前值于节点比较,如果找到,直接更新,没有找到就不断往子节点寻找,直到到达为空的子节点,将值填入到该空节点上。
二叉树查找的时候,查找的效率和树的形状有关,当节点完全平衡时(最底层子节点到根节点的“路程”相同),效率最高;当所有节点都只有一个子节点时,效率最差
2. B树
B-树是一种多路搜索树(并不一定是二叉的)
如下图,节点内有多个值(多路):
上图展示的最多只有两个三个子节点的B树,这种简单结构的树可以称为2-3树,这里点到为止,后续将通过这种树去分析后续的红黑树
3. 2-3树
2-3树允许每个节点保存1个或2个值,含有1个值节点称为2-node(有两个子节点),含有2个值的节点称为3-node(有三个子节点)。
如图:
在2-3树中查找,与二叉树类似,通过不断和节点比较进行向下查找,需要注意的是,在3-node节点中,需要同时比较两个值,如果介于两个值之间,需要找的子节点就是中间的子节点。
这里重点讨论一下插入操作,首先看一种简单的,往2-node节点插入;如果找到的末节点是一个2-node的节点,直接在此节点上新增当前值,将其节点变成3-node节点,如图:
当插入的节点是3-node节点时,应该怎么操作?首先看一下一个最简单的,只有一个3-node节点的树:
首先,将值插入到当前的3-node节点,将其变成4-node节点,然后提取中间的值,让其分解为一个普通的二叉树即可。
那么如果当前插入的3-node节点有父节点时应该怎么处理呢,首先还是将3-node变成4-node节点,然后拆解出一个父节点插入到原来的父节点中,如果当前父节点是2-node节点,那么将其变成3-node节点,如果原来父节点已经是3-node节点,那么循环上面的处理步骤。
总结一下步骤:
- 查找需要插入值的位置
- 判断当前插入节点是否是2-node节点,如果是,则将其变成3-node节点,如不是,继续执行一下步骤
- 将当前节点变成4-node节点
4.分解当前4-node节点,判断当前节点是否为根节点,如果是,将整个2-3树,提高一层,如不是,继续执行一下步骤 - 新增的上层节点插入到父节点中,重复执行2-4步,直到结束
4. 红黑树
红黑树是一种解决平衡二叉树的方法。红黑树具有以下性质:
- 节点是红色或黑色。
- 根节点是黑色。
- 每个叶节点(NIL节点,空节点)是黑色的。
- 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
5.从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
【摘自百度百科】
红黑树其实是2-3树的一种二叉树表现方法,如果将红线连接线水平连接,那么连接的两个节点就是2-3树中的3-node节点,其他没有红线连接的就是2-3树中的2-node节点,如图:
在讲解插入操作前,先了解集中红黑树的平衡操作。第一种是左旋转和右旋转:
第二种是颜色反转,当出现一个节点下的两个节点均是红色时,可以转换到2-3树中思考,这是一个4-node节点,那么需要将其分解为一个二叉树,并向上合并:
有了2-3树的插入讲解,这边,直接分析最复杂的红黑树插入:
- 查找需要插入值的位置
- 新插入的节点元素用红色标识(2-3树中插入到2-node节点,将其变成3-node节点)
- 如果出现节点下的两个子节点都是红色时(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平衡红黑树操作。
先偷一下懒,里面的平衡源码就不分析了。