按照学习习惯:
1. what
红黑树是什么? 排序二叉树的一种,针对排序二叉树在有序输入的情况下会成为链表,添加了5个性质,构造出来的一种最长路径不会超过最短路径的2倍,所以在最坏情况下检索,插入和删除的时间复杂度是 O(lgn). 五个性质分别是什么:1 . 根节点必须是黑. 2. 叶子节点是黑而且是null 3.红节点的子节点必须是黑。 4. 从同一个节点出发的每条路径的黑色高度相同。5. 要么红要么黑
红黑的删除操作:上面的JDK源码研究了红黑树的插入,下面的说明了删除
2. why
为什么红黑树可以做到前人(排序树)做不到的事情!下面说明原因:
(1)每次插入的节点都标为红色
(2) 从根节点出发的所有路径的黑色高度相同
(3)红节点的子节点必须是黑
(4) 根节点必须是黑
从第2点和第3点,就可以得到最长路径不会超过最短路径的2倍,所以能实现O(lgn)
3. where
(1)JDK Collection TreeMap
(2)关联数组
(3) 字典实现
(4)内存中缓存的(区块-数据)
4. example
下面研究JDK的TreeMap源码:
首先有个comparator定制的比较器
有个 Entry<K,V>
size 和 modCount
Entry<K,V>的结构:
六个成员变量, key , value, parent, left , right , color
Entry<K,V> 的构造方式很简单,主要是看看内部成员
TreeMap有四种构造器:
1. 空
2. 一个comparator
3. 一个Map
4. SortedMap带有一个comparator把他给这个TreeMap的comparator
final Entry<K,V> getEntry(Object key) {
// Offload comparator-based version for sake of performance
if (comparator != null)
return getEntryUsingComparator(key);
if (key == null)
throw new NullPointerException();
Comparable<? super K> k = (Comparable<? super K>) key;
Entry<K,V> p = root;
// 从root开始往下循环,使用key(SortedMap的key必须实现comparable接口)的compareTo
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;
}
final Entry<K,V> getEntryUsingComparator(Object key) {
K k = (K) key;
// 这里的Comparator必须是key的K的父类或者父接口
Comparator<? super K> cpr = comparator;
if (cpr != null) {
Entry<K,V> p = root;
while (p != null) {
// 使用comparator的compare method来比较,原理其实和上面的compareTo method一样
int cmp = cpr.compare(k, p.key);
if (cmp < 0)
p = p.left;
else if (cmp > 0)
p = p.right;
else
return p;
}
}
return null;
}
这里有TreeMap的一篇文章: TreeMap源码分析
private void fixAfterInsertion(Entry<K,V> x) {
x.color = RED;
// 这里就可以判断 祖父是红
while (x != null && x != root && x.parent.color == RED) {
// 此情况符合1个条件:1. 父亲是祖父的左孩子
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
Entry<K,V> y = rightOf(parentOf(parentOf(x)));
// 此情况符合2个条件:1. 父亲是祖父的左孩子
// 2. 叔叔是红
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
/* x的祖父是新的 x 知道更新到 循环条件不成立。
能直接进行循环的原因是通过上述可知不会造成黑色高度有变 */
x = parentOf(parentOf(x));
}
// 此情况符合2个条件:1. 父亲是祖父的左孩子
// 2. 叔叔是黑
else {
// 此情况符合3个条件:1. 父亲是祖父的左孩子
// 2. 叔叔是黑
// 3. 儿子是父亲的右孩子
if (x == rightOf(parentOf(x))) {
x = parentOf(x);
rotateLeft(x);
}
// 此情况符合3个条件:1. 父亲是祖父的左孩子
// 2. 叔叔是黑
// 3. 儿子是父亲的左孩子
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateRight(parentOf(parentOf(x)));
}
}
// 此情况符合1个条件:1. 父亲是祖父的右孩子
else {
Entry<K,V> y = leftOf(parentOf(parentOf(x)));
// 此情况符合2个条件:1. 父亲是祖父的右孩子
// 2. 叔叔是红
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
}
// 此情况符合2个条件:1. 父亲是祖父的右孩子
// 2. 叔叔是黑
else {
// 此情况符合2个条件:1. 父亲是祖父的右孩子
// 2. 叔叔是黑
// 3. 儿子是父亲的左孩子
if (x == leftOf(parentOf(x))) {
x = parentOf(x);
rotateRight(x);
}
// 此情况符合2个条件:1. 父亲是祖父的右孩子
// 2. 叔叔是黑
// 3. 儿子是父亲的右孩子
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateLeft(parentOf(parentOf(x)));
}
}
}
root.color = BLACK;
}
前提: 1. 插入节点的父亲是红色,进入调整函数的循环部分
其实可以说出个流程: 条件有三: 1. 父亲是祖父的左/右孩子
2.叔叔是红/黑
3. 儿子是父亲的左/右孩子
注意点: 1. 叔叔是红不用旋转,但是可能会影响到祖父一代的黑色高度。
2. 叔叔是黑,把父亲和儿子旋转到同一阵线。以祖父为轴旋转
下面研究删除:
//返回后继节点,t是待删除节点,返回的是他的后继节点
static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
if (t == null)
return null;
// 待删除节点有右孩子
else if (t.right != null) {
Entry<K,V> p = t.right;
//遍历直到右孩子的一个没有左孩子的节点。
while (p.left != null)
p = p.left;
//这就是他的后继,返回
return p;
} else {
//无右孩子
Entry<K,V> p = t.parent;
Entry<K,V> ch = t;
//实际上还是找后继,二叉搜索树找后继很麻烦
while (p != null && ch == p.right) {
ch = p;
p = p.parent;
}
return p;
}
}
要研究删除首先要考虑二叉搜索树的后继,不过这个不是重点。
这里需要注意,后继只可能有右子树。
这里有一个前提:只有当删除的节点是黑色的时候才会需要调用调整函数
private void deleteEntry(Entry<K,V> p) {
modCount++;
size--;
// If strictly internal, copy successor's element to p and then make p
// point to successor.
// 恩,明白了。
if (p.left != null && p.right != null) {
Entry<K,V> s = successor(p);
p.key = s.key;
p.value = s.value;
p = s;
} // p has 2 children
// Start fixup at replacement node, if it exists.
Entry<K,V> replacement = (p.left != null ? p.left : p.right);
if (replacement != null) {
// Link replacement to parent
// replacement向上移动,p被删除
replacement.parent = p.parent;
if (p.parent == null)
root = replacement;
else if (p == p.parent.left)
p.parent.left = replacement;
else
p.parent.right = replacement;
// Null out links so they are OK to use by fixAfterDeletion.
// 正式删除p节点
p.left = p.right = p.parent = null;
// Fix replacement
// 如果删除的后继节点p是黑色的话
// 如果是红色,那么他的子几点必定是黑色,黑的子节点对性质4和5是无影响的。
if (p.color == BLACK)
// 通过旋转和重涂调整
fixAfterDeletion(replacement);
} else if (p.parent == null) { // return if we are the only node.
root = null;
} else { // No children. Use self as phantom replacement and unlink.
if (p.color == BLACK)
// 当删除的节点是黑色的时候才需要fix
fixAfterDeletion(p);
//下面是通过直接set空来处理
if (p.parent != null) {
if (p == p.parent.left)
p.parent.left = null;
else if (p == p.parent.right)
p.parent.right = null;
p.parent = null;
}
}
}
下面是 删除调整函数:
前提:代替节点为黑才能进入调整函数的循环体内