红黑树定义:
红黑树是一种含有红黑结点并能自平衡的二叉查找树。它必须满足下面性质:
-
性质1:每个节点要么是黑色,要么是红色。
-
性质2:根节点是黑色。
-
性质3:每个叶子节点是黑色。
-
性质4:每个红色结点的两个子结点一定都是黑色。
-
性质5:任意一结点到每个叶子结点的路径都包含数量相同的黑结点。(保持平衡)
总结:红色节点不可能相连,黑色节点可能相连。
红黑树自平衡基本操作:
-
变色:在不违反上述红黑树规则特点情况下,将红黑树某个node节点颜色由红变黑,或者由黑变红;
-
左旋:逆时针旋转两个节点,让一个节点被其右子节点取代,而该节点成为右子节点的左子节点
-
右旋:顺时针旋转两个节点,让一个节点被其左子节点取代,而该节点成为左子节点的右子节点
HashMap中红黑树节点的结构:
需要三个指针就够了,基本的左右指针,指向父节点的指针。然后颜色标识符:red
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent; // red-black tree links 父节点
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev; // needed to unlink next upon deletion
boolean red;
...
}
对于红黑树来说,查找是比较简单的,和普通二叉树没有区别,难点在于构建红黑树,删除节点,会有很多情况。
1.查找节点
final Entry<K,V> getEntry(Object key) {
Entry<K,V> p = root;
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;
}
2.添加元素
第一步插入,和普通的排序二叉树插入一样
第二步则是一个调整的过程。因为红黑树不一样,当我们添加一个新的元素之后可能会破坏它固有的属性。主要在于两个地方,一个是要保证新加入元素后,到所有叶节点的黑色节点还是一样的。另外也要保证红色节点的子节点为黑色节点。
还有一个就是,结合TreeMap的map特性,我们添加元素的时候也可能会出现新加入的元素key已经在数中间存在了,那么这个时候就不是新加入元素,而是要更新原有元素的值。
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;
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); //t为空,就退出,找到存放位置
Entry<K,V> e = new Entry<>(key, value, parent); //根据父节点,创建新的节点。
if (cmp < 0) //根据cmp的值,设置parent的子节点,此处就成功插入了
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e); //调整,【重点】
size++;
modCount++;
return null;
}
2.1 调整红黑树
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) { //如果是红色 --------------情况1
setColor(parentOf(x), BLACK); //新插入节点的上层设置为黑色
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x)); //取到父父节点
} else { //--------------------情况2
if (x == rightOf(parentOf(x))) { //如果新插入的节点是右节点,需要调整到左节点
x = parentOf(x);
rotateLeft(x); //左选,可见下面图片,情况2
}
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) { //-----------------------情况3
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else { //---------------------------情况4
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 :里面实则有两种情况,但是可以进行简单的化归
然后对G进行右旋,右旋之后,重新上色
情况3:也就是情况1的反向操作,新插入节点的父节点是右节点
情况4:自然和情况2相对,这里就不展示了
颜色设置好之后,需要进行左选,右旋操作
左选:注意传入的参数:一个节点
两个节点,进行旋转,他们的排版其实存在两种情况,传入节点为:左节点 or 右节点
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的左子节点,成功转到p节点的右子节点。
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;
}
}
我们这里传入的是P节点,注意旋转,不是简单的交换子节点的位置,要保证二叉树的有序性,所以左中右的顺序不能改变。
1.传入节点是左节点
2.传入节点是右节点
左旋第一个节点:PP,
总结:传入节点的左子节点不会发生变化,右节点接收右子节点的左子节点,右子节点提升一级,传入节点变成左子节点。
右旋:
private void rotateRight(Entry<K,V> p) {
if (p != null) {
Entry<K,V> l = p.left;
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)
p.parent.right = l;
else p.parent.left = l;
l.right = p;
p.parent = l;
}
}
右旋:
此处就不分析P为左节点情况,同理可得。
参考链接:
1.https://www.jianshu.com/p/e136ec79235c