红黑树
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
红黑树的性质
- 每个结点不是红色就是黑色;
- 根节点是黑色的 ;
- 如果一个节点是红色的,则它的两个孩子结点是黑色的 ;
- 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点 ;
- 每个叶子结点都是黑色的(此处的叶子结点指的是空结点);
满足上面的性质,红黑树就能保证:最长边的长度<=2*最短边的长度
,红黑树中的平衡是一种相对平衡,而不像AVL树那样绝对平衡。红黑树中插入/查找的时间复杂度也是O(log(N))
。
红黑树节点的定义
class RBTreeNode{
RBTreeNode left = null;
RBTreeNode right = null;
RBTreeNode parent = null;
COLOR color = RED; // 节点的颜色
int val;
public RBTreeNode(int val){
this.val = val;
}
}
1 红黑树的查找
同二叉搜索树相同。
2 红黑树的插入(插入的节点颜色一定是红色的)
红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:
(1). 按照二叉搜索的树规则插入新节点.
(2). 检测新节点插入后,红黑树的性质是否造到破坏.
下面分三种情况去讨论:
- 插入的节点为根节点。(根必须是黑色的,改下颜色即可)
- 插入节点的父节点是黑色的。(无需调整,插入结束)
- 插入节点的父节点是红色的。(红色不能相邻,需要进一步调整,再分情况)
针对父节点是红色的情况:
1)其父(parent)节点有兄弟(brother)节点存在且是红色的:
此时只需将其parent.parent
改为红色,parent,brother
改为黑色即可。因为parent.paprent
改为了红色,所以需要继续递归向上调整。
2)其父节点没有brother节点,或者brother节点为黑色
A. 同边的情况(右旋或者左旋):
此时需要将p.p
右旋,并将parent
改为黑色,p.p
改为红色。
对于brother为黑色的这种情况,可推知cur一定不是新插入的节点,而是在调整过程中变红的节点(如果是新插入的节点,那么原来的的树不满足“每个路径上的黑色节点相同”这一性质,所以不成立)。
所以这种情况下,cur下面应该带有子树。对p.p右旋,prent改黑色,p.p改红色。
B. 不同边的情况(右旋或者左旋)
先对parent左旋,变为同边的情况,操作同1)中所述。
3 AVL树和红黑树的比较
红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(log(N))
,红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。AVL树的查找性能略优,红黑树的插入删除性能略优。 java集合框架中的:TreeMap、TreeSet底层使用的就是红黑树.
4 红黑树插入举例:
5 TreeMap源码:
红黑树的插入:
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;
}
插入之后的调整:
/** 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)));
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 {
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 static <K,V> Entry<K,V> parentOf(Entry<K,V> p) {
return (p == null ? null: p.parent);
}