Java8中的红黑树
红黑树的性质
-
节点是红色或黑色
-
根节点是黑色
-
每个叶子节点都是黑色的空节点
-
每个红色节点的两个子节点都是黑色(任何路径上不能有两个连续的红色节点)
-
从任一节点到其每个叶子节点的所有路径都包含相同数目的黑色节点
TreeMap中的红黑树
1. 数据结构
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;
/**
* Make a new cell with given key, value, and parent, and with
* {@code null} child links, and BLACK color.
*/
Entry(K key, V value, Entry<K,V> parent) {
this.key = key;
this.value = value;
this.parent = parent;
}
...
}
2. 插入节点
public V put(K key, V value) {
Entry<K,V> t = root; // 插入新节点时,如果没有根节点,则将新节点作为根节点
if (t == null) {
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;
// 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);
}
else {
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
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);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e); // 插入新节点后,原有的红黑树的平衡有可能会被打破
size++; // 需要通过#变色或则旋转#使其再次达到自平衡
modCount++;
return null;
}
fixAfterInsertion(e): 平衡红黑树
红黑树平衡规则分类:
- 新节点是根节点,直接变为黑色
- 新节点的父亲节点是黑色,不需要改变
- 新节点的父亲节点是红色,叔叔节点是红色 调整:父亲节点的颜色变为黑色,叔叔节点的颜色变为黑色,爷爷节点的颜色变为黑色
- 新节点的父亲节点是红色,新节点的父亲节点是爷爷节点的左孩子,叔叔节点是黑色或则没有叔叔节点,新节点是父亲节点的右孩子 调整:先左旋再右旋
- 新节点的父亲节点是红色,新节点的父亲节点是爷爷节点的左孩子,叔叔节点是黑色或则没有叔叔节点,新节点是父亲节点的左孩子 调整:只需右旋
- 新节点的父亲节点是红色,新节点的父亲节点是爷爷节点的右孩子,叔叔节点是黑色或则没有叔叔节点,新节点是父亲节点的左孩子 调整:先右旋再左旋
- 新节点的父亲节点是红色,新节点的父亲节点是爷爷节点的右孩子,叔叔节点是黑色或则没有叔叔节点,新节点是父亲节点的右孩子 调整:只需左旋
/** From CLR */ // 根据算法导论
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))); // y 新节点的爷爷节点的右孩子 (即新节点的叔叔节点)
if (colorOf(y) == RED) { // 叔叔节点是红色节点 (符合分类 3)
setColor(parentOf(x), BLACK); // 父亲节点的颜色变为黑色
setColor(y, BLACK); // 叔叔节点的颜色变为黑色
setColor(parentOf(parentOf(x)), RED); // 爷爷节点的颜色变为黑色
x = parentOf(parentOf(x)); // x 指向爷爷节点 由于树的平衡被改变 需要向上检查整棵树的平衡
} else { // 叔叔节点是黑色 (如果叔叔节点为null, parentof()会默认置为黑色)
if (x == rightOf(parentOf(x))) { // 新节点是父亲节点的右孩子 (符合规则 4)
x = parentOf(x); // 以新节点的父亲节点为轴 旋转
rotateLeft(x); // x 为父亲节点 左旋
} // 如果 新节点是父亲节点的左孩子 只需右旋 (符合规则 5)
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateRight(parentOf(parentOf(x)));
}
} else { // 新节点的父亲节点是爷爷节点的右孩子
Entry<K,V> y = leftOf(parentOf(parentOf(x))); // y 新节点的爷爷节点的左孩子 (即新节点的叔叔节点)
if (colorOf(y) == RED) { // 叔叔节点是红色节点 (符合分类 3)
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else { // 叔叔节点是黑色 (如果叔叔节点为null, parentof()会默认置为黑色)
if (x == leftOf(parentOf(x))) { // 新节点是父亲节点的左孩子 (符合规则 6)
x = parentOf(x);
rotateRight(x); // 右旋
} // 如果 新节点是父亲节点的右孩子 只需左旋 (符合规则 7)
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateLeft(parentOf(parentOf(x)));
}
}
}
root.color = BLACK;
}
左旋: 新节点的左孩子变为父亲节点的右孩子,父亲节点变为新节点的左孩子
/** From CLR */
private void rotateLeft(Entry<K,V> p) {
if (p != null) { // p 新节点的父亲节点
Entry<K,V> r = p.right; // r 父亲节点的右孩子 (即 新节点自己)
p.right = r.left; // 父亲节点的右孩子引用 指向 新节点的左孩子
if (r.left != null) // 如果新节点的左孩子为null 则说明父亲节点的右孩子引用 指向 null
r.left.parent = p; // 新节点的左孩子不为null 新节点的左孩子的父亲节点引用 指向 新节点父亲节点
r.parent = p.parent; // 新节点的父亲节点引用 指向 新节点的爷爷节点
if (p.parent == null) // 爷爷节点为null 这种情况将新节点设置为根节点
root = r;
else if (p.parent.left == p) // 父亲节点为爷爷节点的左孩子 (规则 4)
p.parent.left = r; // 爷爷节点的左孩子引用 指向 新节点
else // 父亲节点为爷爷节点的右孩子 (规则 7)
p.parent.right = r; // 爷爷节点的右孩子引用 指向 新节点
r.left = p; // 新节点的左孩子引用 指向 父亲节点
p.parent = r; // 父亲节点的父亲引用 指向 新节点
}
}
右旋: 新节点的右孩子变为父亲节点左孩子,父亲节点变为新节点的右孩子
/** From CLR */ // 右旋为左旋的镜像
private void rotateRight(Entry<K,V> p) {
if (p != null) {
Entry<K,V> l = p.left; // p 为父亲节点 l 为新节点自己
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) // (规则 6)
p.parent.right = l;
else p.parent.left = l; // (规则 5)
l.right = p; // 改变引用的指向 将父亲节点变为新节点的右孩子
p.parent = l;
}
}