TreeMap底层以黑红树数据结构存储数据,实现了Map.Entry接口,key/value记录实际数据,left/right/parent记录左右子节点和父节点,color标识节点颜色,只有红黑两种颜色,所以使用boolean类型。
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;
}
先来回滚下红黑树的特性,红黑树是一种自平衡的二叉查找树,它在二叉查找树的基础上又具备如下特征:
- 节点是红色或黑色。
- 根节点是黑色。
- 每个叶子节点都是黑色的空节点(NIL节点)。
- 不能有两个连续的红色节点 (有红必有黑,红红不相连)
- 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。(相同的黑高)
左旋:
1.左旋的目标节点X往左下沉
2.右子节点替代它的位置
3.右子节点的左子树转移到目标节点的右子树上(因为Y>b>X,仍然满足平衡二叉树特性)
右旋:
下面主要解析下红黑树插入/删除后重新平衡的源码。
/** From CLR */
private void rotateLeft(Entry<K,V> p) {
// 把p往左下调整一级;
// p的右子节点替代p的位置,
// p的右子节点的左子树挂在p的右子树上;
// 调整受影响节点的指针
if (p != null) {
Entry<K,V> r = p.right; // r记录p的右子节点
p.right = r.left;// 将r的左子树挂在p的右子树上
if (r.left != null)//如果r的左子树不空,则把父节点指针指向p
r.left.parent = p;
r.parent = p.parent;//r替换p的位置,r的父节点指针指向p的父节点
if (p.parent == null)//如果p的父节点为空,则将r置为root
root = r;
else if (p.parent.left == p)//如果p是父节点的左子节点,则将p的父节点的左子节点置为r
p.parent.left = r;
else // 否则将右子节点置为r
p.parent.right = r;
r.left = p; // r的左节点置为p
p.parent = r; // p的父节点置为r
}
}
// 右旋逻辑类似,不再贴出
下面是插入节点后的调整动作,特别关注标注2,如果叔叔节点是黑色而父节点是红色,那说明当前的x并非存插入节点,而是由于子树上有新插入节点后调整导致的x由黑变红,所以x以下的黑高和y以下的黑高(含y)是相等的,理解这一点特别重要。
/** From CLR */
private void fixAfterInsertion(Entry<K,V> x) {
x.color = RED;
while (x != null && x != root && x.parent.color == RED) { // 进入循环条件,x不空也不是root,且父节点和x都是红色
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) { // 如果x的父节点是其祖父节点的左子节点
Entry<K,V> y = rightOf(parentOf(parentOf(x))); // 取y=x的祖父节点的右子节点
if (colorOf(y) == RED) { //1. 如果y是红色,则已满足黑路径平衡条件
setColor(parentOf(x), BLACK); // 将父节点和父节点的兄弟节点置为黑色
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED); // 将祖父节点置为红色,仍然满足黑路径平衡条件,且x和父节点的颜色不再冲突
x = parentOf(parentOf(x)); // x替换为祖父节点
} else { // 2. 如果y是黑色或者NIL节点,特别的,若y非空,则x并非新插入节点(x和兄弟节点在进入循环前都是黑色),是由于x的子树下存在新插入节点导致x变色为红色。
if (x == rightOf(parentOf(x))) { //2.1 如果x是父节点的右子节点,说明父节点还有个左子节点是黑色
x = parentOf(x); // x替换为父节点
rotateLeft(x); // 对x左旋:x下沉一级,
} //2.2 如果不满足if,则说明x是父节点的左子节点且x,同时父节点还有个右子节点是黑色
setColor(parentOf(x), BLACK); // 将父节点置为黑色
setColor(parentOf(parentOf(x)), RED); // 将祖父节点置为红色
rotateRight(parentOf(parentOf(x))); // 对祖父节点右旋:祖父节点向右下沉一级,父节点上升一级,兄弟节点被转移到祖父节点的左子树上。
}
} else { // 与上面类似,获取叔叔节点的方式和旋转方向不一样
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;
}
插入后调整的分支主要分两种情况:
1.叔叔节点是红色,只需将父节点和叔叔节点都染成黑色,将祖父节点染成红色,这样祖父节点以下的黑高维持不变,然后以祖父节点为参照继续往上递归调整。
2.叔叔节点为空或者黑色,这种情况就需要最多2次旋转来调整,而且旋转完因为局部根节点已经是黑色,所以会退出循环,调整结束。
2.1.当前节点N是父节点的右子节点,则选择N的父节点作为目标节点,进行左旋,然后并入步骤2.2
2.2.当前节点N是父节点的左子节点,则将父节点染成黑色,祖父节点染成红色,然后对祖父节点右旋。
需要注意的是,如果叔叔节点非空,则N并非新插入节点,而是在N的子树上有新插入节点,在调整过程中将N由黑变红了,所以在下图中的树只是整个树的局部展示,左右子树的黑高不相等,这也是上面提到要特别关注代码中标注2的原因。
参考:https://juejin.im/entry/58371f13a22b9d006882902d