文章转载自:http://www.javashuo.com/article/p-orraekjs-n.html,以为做者写的很是好,特此转载此文章方便学习,如若侵权,立马删除!php
本文以Java TreeMap为例,从源代码层面,结合详细的图解,剥茧抽丝地讲解红黑树(Red-Black tree)的插入,删除以及由此产生的调整过程。 html
前言
Java TreeMap实现了SortedMap接口,也就是说会按照key
的大小顺序对Map中的元素进行排序,key
大小的评判能够经过其自己的天然顺序(natural ordering),也能够经过构造时传入的比较器(Comparator)。 java
TreeMap底层经过红黑树(Red-Black tree)实现,也就意味着containsKey()
, get()
, put()
, remove()
都有着log(n)
的时间复杂度。其具体算法实现参照了《算法导论》。
出于性能缘由,TreeMap是非同步的(not synchronized),若是须要在多线程环境使用,须要程序员手动同步;或者经过以下方式将TreeMap包装成(wrapped)同步的:程序员
SortedMap m = Collections.synchronizedSortedMap(new TreeMap(...));
web
红黑树是一种近似平衡的二叉查找树,它可以确保任何一个节点的左右子树的高度差不会超过两者中较低那个的一陪。具体来讲,红黑树是知足以下条件的二叉查找树(binary search tree):算法
- 每一个节点要么是红色,要么是黑色。
- 根节点必须是黑色
- 红色节点不能连续(也便是,红色节点的孩子和父亲都不能是红色)。
- 对于每一个节点,从该点至
null
(树尾端)的任何路径,都含有相同个数的黑色节点。
在树的结构发生改变时(插入或者删除操做),每每会破坏上述条件3或条件4,须要经过调整使得查找树从新知足红黑树的条件。多线程
预备知识
当查找树的结构发生改变时,红黑树的条件可能被破坏,须要经过调整使得查找树从新知足红黑树的条件。调整能够分为两类:一类是颜色调整,即改变某个节点的颜色;另外一类是结构调整,集改变检索树的结构关系。结构调整过程包含两个基本操做:左旋(Rotate Left),右旋(RotateRight)。 app
左旋
左旋的过程是将x
的右子树绕x
逆时针旋转,使得x
的右子树成为x
的父亲,同时修改相关节点的引用。旋转以后,二叉查找树的属性仍然知足。
TreeMap中左旋代码以下:svg
//Rotate Left
private void rotateLeft(Entry<K,V> p) {
if (p != null) {
Entry<K,V> r = p.right;
p.right = r.left;
if (r.left != null)
r.left.parent = p;
r.parent = p.parent;
if (p.parent == null)
root = r;
else if (p.parent.left == p)
p.parent.left = r;
else
p.parent.right = r;
r.left = p;
p.parent = r;
}
}
右旋
右旋的过程是将x
的左子树绕x
顺时针旋转,使得x
的左子树成为x
的父亲,同时修改相关节点的引用。旋转以后,二叉查找树的属性仍然知足。
TreeMap中右旋代码以下: 函数
//Rotate Right
private void rotateRight(Entry<K,V> p) {
if (p != null) {
Entry<K,V> l = p.left;
p.left = l.right;
if (l.right != null) l.right.parent = p;
l.parent = p.parent;
if (p.parent == null)
root = l;
else if (p.parent.right == p)
p.parent.right = l;
else p.parent.left = l;
l.right = p;
p.parent = l;
}
}
方法剖析
get()
get(Object key)
方法根据指定的key
值返回对应的value
,该方法调用了getEntry(Object key)
获得相应的entry
,而后返回entry.value
。所以getEntry()
是算法的核心。算法思想是根据key
的天然顺序(或者比较器顺序)对二叉查找树进行查找,直到找到知足k.compareTo(p.key) == 0
的entry
。
具体代码以下:
//getEntry()方法
final Entry<K,V> getEntry(Object key) {
......
if (key == null)//不容许key值为null
throw new NullPointerException();
Comparable<? super K> k = (Comparable<? super K>) key;//使用元素的天然顺序
Entry<K,V> p = root;
while (p != null) {
int cmp = k.compareTo(p.key);
if (cmp < 0)//向左找
p = p.left;
else if (cmp > 0)//向右找
p = p.right;
else
return p;
}
return null;
}
put()
put(K key, V value)
方法是将指定的key
, value
对添加到map
里。该方法首先会对map
作一次查找,看是否包含该元组,若是已经包含则直接返回,查找过程相似于getEntry()
方法;若是没有找到则会在红黑树中插入新的entry
,若是插入以后破坏了红黑树的约束,还须要进行调整(旋转,改变某些节点的颜色)。
代码以下:
public V put(K key, V value) {
......
int cmp;
Entry<K,V> parent;
if (key == null)
throw new NullPointerException();
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++;
return null;
}
上述代码的插入部分并不难理解:首先在红黑树上找到合适的位置,而后建立新的entry
并插入(固然,新插入的节点必定是树的叶子)。难点是调整函数fixAfterInsertion()
,前面已经说过,调整每每须要:
1.改变某些节点的颜色
2.对某些节点进行旋转。
调整函数fixAfterInsertion()
的具体代码以下,其中用到了上文中提到的rotateLeft()
和rotateRight()
函数。经过代码咱们可以看到,状况2实际上是落在状况3内的。状况4~状况6跟前三种状况是对称的,所以图解中并无画出后三种状况,读者能够参考代码自行理解。
//红黑树调整函数fixAfterInsertion()
private void fixAfterInsertion(Entry<K,V> x) {
x.color = RED;
while (x != null && x != root && x.parent.color == RED) {
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
Entry<K,V> y = rightOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
//若是y为null,则视为BLACK
setColor(parentOf(x), BLACK); // 状况1
setColor(y, BLACK); // 状况1
setColor(parentOf(parentOf(x)), RED); // 状况1
x = parentOf(parentOf(x)); // 状况1
} else {
if (x == rightOf(parentOf(x))) {
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<K,V> y = leftOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK); // 状况4
setColor(y, BLACK); // 状况4
setColor(parentOf(parentOf(x)), RED); // 状况4
x = parentOf(parentOf(x)); // 状况4
} else {
if (x == leftOf(parentOf(x))) {
x = parentOf(x); // 状况5
rotateRight(x); // 状况5
}
setColor(parentOf(x), BLACK); // 状况6
setColor(parentOf(parentOf(x)), RED); // 状况6
rotateLeft(parentOf(parentOf(x))); // 状况6
}
}
}
root.color = BLACK;
}
remove()
remove(Object key)
的做用是删除key
值对应的entry
,该方法首先经过上文中提到的getEntry(Object key)
方法找到key
值对应的entry
,而后调用deleteEntry(Entry<K,V> entry)
删除对应的entry
。因为删除操做会改变红黑树的结构,有可能破坏红黑树的约束,所以有可能要进行调整。
告知
有关remove()的具体讲解将放到下一篇博文当中,敬请期待!
本文分享 CSDN - 鄙人薛某。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。