首先先看TreeMap的继承关系:
继承了抽象类AbstractMap,实现了NavigableMap(SortedMap)、Cloneable、Serializable三个接口
- NavigableMap(SortedMap):使Key有序,可以获取头尾K-V对,或者获取指定范围内的SubMap
- Cloneable:支持clone方法
- Serializable:支持序列化
基于红黑树实现:
// Red-black mechanics
private static final boolean RED = false;
private static final boolean BLACK = true;
/**
* Node in the Tree. Doubles as a means to pass key-value pairs back to
* user (see Map.Entry).
*/
static final class Entry<K,V> implements Map.Entry<K,V> {
K key;
V value;
Entry<K,V> left;
Entry<K,V> right;
Entry<K,V> parent;
boolean color = BLACK;
……
}
红黑树特点:
- 根节点必为黑色
- 叶节点可红可黑
- 每条路径上黑节点数量(即黑深度)相等
- 父子节点不能同时为红色
- 最长路径的长度不超过最短路径长度的2倍即
相比于AVL树,红黑树插入时旋转次数基本一致,但是回溯步长为2,耗时更短;删除时旋转次数最多3次,AVL树最多O(logn)次,红黑树性能更好。但是红黑树一般更高(更不平衡)
TreeMap要求,要么节点的Key值实现了Comparable接口,要么传入一个合适的Comparator对象:
final int compare(Object k1, Object k2) {
return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
: comparator.compare((K)k1, (K)k2);
}
并且优先使用Comparator进行比较
因此不需要重写Key的hashCode方法和equals方法。因为根本没用到这两个方法,像HashMap就会对key值进行比较:
//HashMap类,putVal方法
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
如果两个要求都没有达成,则抛出ClasscastException
节点插入:put方法
先贴源码:
public V put(K key, V value) {
//先把root赋给当前节点,如果为空,代表是棵空树,则直接插入一个新节点
Entry<K,V> t = root;
if (t == null) {
//用来检测Key是否实现了Comparable或TreeMap是否传入了Comparator
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
Comparator<? super K> cpr = comparator;
if (cpr != null) {
//不断地将传入的key值与当前节点的key值进行比较
//如果传入的key值更大,则向右走,更小则向左,相等就直接进行值覆盖
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);
}
else {
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
//这里的过程和if分支的一样
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);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
//fixAfterInsertion方法进行红黑树调整
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
可以看到,整个插入操作都没有涉及树的调整,直到插入完成才会进行
调整包括重新着色和左右旋转
private void fixAfterInsertion(Entry<K,V> x) {
//先把新插入节点设为红色,内部类Entry的默认值为BLACK
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) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
//然后将祖父节点作为当前节点进入下一轮循环
x = parentOf(parentOf(x));
} else {
//如果叔叔是黑色,且自己是父节点的右子节点,就对父节点进行左旋
if (x == rightOf(parentOf(x))) {
x = parentOf(x);
rotateLeft(x);
}
//把父节点染黑、祖父节点染红,对祖父节点进行右旋
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateRight(parentOf(parentOf(x)));
}
} else {
//以下跟if分支类似
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;
}
红黑树插入操作中,如果发生了树的调整,存在以下三种情形:
- 父节点和叔节点都是红色
- 父节点是红色,叔节点是黑色
- 自身是父节点左子结点,则右旋
- 否则左旋
旋转代码如下(左旋):
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;
}
}
左旋的过程就是右儿子当爹,自己变成左儿子,自己的孙节点变成右子节点;右旋反之
以书上的例子说明:按照 :插入 55 56 57 58 83 ,删除57 ,插入59 的顺序建树
图直接从书上拍照过来了:
55直接插入、染黑就行,56染红,57插入时,出现连续红节点,由于默认null节点是黑色,于是发生左旋
插入58时,又出现连续红色,此时父叔节点都是红色,则仅触发重新着色,不进行旋转,56从红变黑是因为根节点每次调整后都会染黑
插入83时再次需要调整,此时情况和57插入时类似,发生了左旋
57因为是叶节点,又是红色,因此可以直接删除
59插入时,再次进行调整,叔节点黑色,自己是左子结点,于是先右旋,右旋之后又触发了左旋条件,于是进行左旋
书上的说明到此就结束了
节点删除:实际调用deleteEntry方法
先贴源码:
private void deleteEntry(Entry<K,V> p) {
modCount++;
size--;
// 如果p不是叶节点,就选一个继任者接替p,然后让p指向这个接任者
if (p.left != null && p.right != null) {
Entry<K,V> s = successor(p);
p.key = s.key;
p.value = s.value;
p = s;
}
Entry<K,V> replacement = (p.left != null ? p.left : p.right);
//如果p至少有一个儿子
if (replacement != null) {
// 让p的儿子认爹
replacement.parent = p.parent;
if (p.parent == null)
root = replacement;
//让p的爹认儿子
else if (p == p.parent.left)
p.parent.left = replacement;
else
p.parent.right = replacement;
// 其他人都有儿子和爹了,p没有作用,因此可以孤立出来了(删除)
p.left = p.right = p.parent = null;
// 树结构调整
if (p.color == BLACK)
fixAfterDeletion(replacement);
} else if (p.parent == null) {
// 如果p没儿子也没爹,说明它是唯一节点(根节点),那么只要把整个树置空即可
root = null;
} else {
// 如果p没儿子但是有爹
// 那就先进行重新着色和旋转
if (p.color == BLACK)
fixAfterDeletion(p);
// 如果调整完p还不是根节点,那么此时p肯定是叶节点,直接删除就好
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;
}
}
}
主要分三种情况:
- p是叶节点:
- p是黑色:先进行树调整,再删除
- p是红色:直接删
- p不是叶节点:先找p的爹或者儿子当遗产继承人,然后让p的儿子认p的爹当爹,让p的爹认p的儿子当儿子,然后把p删掉
- p是黑色:进行树结构调整
successor是找继承人的方法:
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;
}
}
很符合继承法(左儿子没继承权)
fixAfterDeletion和插入的fixAfterInsertion方法类似,也是进行树结构调整的:
private void fixAfterDeletion(Entry<K,V> x) {
//循环条件:当前节点不是根节点,并且颜色是黑色
while (x != root && colorOf(x) == BLACK) {
//如果当前节点是左儿子
if (x == leftOf(parentOf(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) {
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));
//取root,退出循环
x = root;
}
} else {
……//作用差不多,省略了
}
}
//最后确保root是黑色
setColor(x, BLACK);
}
这个调整相当绕人
还是用相同的例子,假设现在要删掉59:
根据代码,会找83代替59,之后只要把多余的83叶节点删除即可,由于该节点是红色,删除也不会违反红黑树特性,因此可以直接删除