红黑树
红黑树
1、每个节点只能是红色或黑色
2、树的根始终是黑色的
3、没有两个相邻的红色节点(红色节点不能有红色父节点或红色子节点,并没有说不能出现连续的黑色节点)
4、从节点(包括根)到其任何后代Null节点(叶子节点下方挂的两个空节点,并且认为它们是黑色的)的每条路径都具有相同数量的黑色节点。
再向红黑树里面添加元素的时候,红黑树主要是通过下面这两个操作来保持平衡的:
1、 recolor(重新标记 黑色或红色)
2、 rotation(旋转)
首先是 recolor,如果重新标记不能够达到红黑树的4点要求。那么我们就需要使用rotation。
假设我们插入的新节点为 X
-
将新插入的节点标记为红色
-
如果 X 是根节点(root),则标记为黑色
-
如果 X 也不是root 同时X 的parent不是黑色 ;
3.1、如果 X 的uncle(叔叔) 是红色
- 将parent 和uncle标记为黑色
- 将 grand parent(祖父)标记为红色
- 让 X节点的颜色和X祖父的颜色相同,然后重复步骤2,3
来看下图
按照上面的公式来:
1、 将新插入的X节点标记为红色
2、发现 X的parent(P)同样为红色,这违反了上面的第三条原则[没有两个相邻的红色节点]
3、发现X的 uncle(U)同样为红色
4、 将P和U 标记为黑色
5、将 X和X的grand parent(G)标记为相同的颜色,即红色,继续重复公式2、3
6、发现G是根节点,标记为黑色
7、 结束
3.2 如果X 的uncle(叔叔)是黑色,就会有下面四种情况
1、 左左(P 是 G 的左孩子,并且 X是 P 的左孩子)
2、 左右(P 是 G 的左孩子,并且 X是 P 的右孩子)
3、 右右(P 是 G 的右孩子,并且 X是 P 的右孩子)
4、 右左(P 是 G 的右孩子,并且 X是 P 的左孩子)
先简单的演示一下怎么旋转的
如下图的红黑树,我们插入的节点是65
左左节点旋转(j插入节点的父节点是左节点,插入节点也是左节点):
可以围绕祖父节点69右旋,再结合变色,步骤如下
左右节点旋转(插入节点的父节点是左节点,插入节点是右节点)
可以先围绕父节点66左旋,然后再围绕祖父节点69右旋,最后再将67设置为黑色,把69设置为红色
通过上面这两个可以发现如果要插入的节点是在右节点,它比左节点多了一步 父节点左旋的操作
如果插入的数据是在右节点,如下图,要插入节点68
右左节点旋转(插入节点的父节点是右节点,插入节点也是左节点)
可以先围绕父节点69右旋,接着在围绕祖父节点66左旋,最后把68节点设置为黑色,把66设置为红色。
右右节点旋转(插入节点的父节点是右节点,插入节点也是右节点)
可以围绕祖父节点66左旋,再把旋转后的根节点69设置为黑色,把66这个节点设置为红色
通过上面两个发现插入的节点为左节点的话,比右节点多了一步父节点右旋的操作
红黑树在Java中的实现
private static final boolean RED = false;
private static final boolean BLACK = true;
// TreeMap里面使用 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;
Entry(K key, V value, Entry<K,V> parent) {
this.key = key;
this.value = value;
this.parent = parent;
}
.....
}
接下来看下 TreeMap的put方法
public V put(K key, V value) {
//先用t来保存 root节点的数据
Entry<K,V> t = root;
//判断当前链表是否为空
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
// 记录修改次数加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;
//将新插入的key和t的key进行比较
cmp = cpr.compare(key, t.key);
//如果新插入的key小于t.key,t等于t左边的节点
if (cmp < 0)
t = t.left;
//如果新插入的key大于t.key,t等于t的右边节点
else if (cmp > 0)
t = t.right;
else
//如果两个key相等,新的value覆盖原有的value,并返回原有的value
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);
}
//将新插入的节点作为parent节点的子节点
Entry<K,V> e = new Entry<>(key, value, parent);
//如果新插入key小于parent的key,则e作为parent的左子节点
if (cmp < 0)
parent.left = e;
//如果新插入key小于parent的key,则e作为parent的右子节点
else
parent.right = e;
//修复红黑树
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
查看红黑树是如何修复的
private void fixAfterInsertion(Entry<K,V> x) {
// 默认给红色的
x.color = RED;
// X节点的父节点不是根节点,并且父节点的颜色是红色,如果父节点是黑色,那就不需要修复
while (x != null && x != root && x.parent.color == RED) {
// 如果 x的父节点是 x 祖父节点的 左节点
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
// 获取 x的 祖父节点的右节点,也就是叔叔节点
Entry<K,V> y = rightOf(parentOf(parentOf(x)));
// 如果x的 叔叔节点是红色
if (colorOf(y) == RED) {
//将X的父节点设置为黑色
setColor(parentOf(x), BLACK);
//将x 的叔叔节点设置为黑色
setColor(y, BLACK);
// 将x 的祖父节点设置为红色
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
//如果x的叔叔节点是黑色
} else {
// 对应的是 左右节点旋转
// 如果x是其父节点的右节点
if (x == rightOf(parentOf(x))) {
// 将x的父节点设置为x
x = parentOf(x);
//左旋转
rotateLeft(x);
}
//把x的父节点设置为黑色
setColor(parentOf(x), BLACK);
// 把x的祖父节点设置为红色
setColor(parentOf(parentOf(x)), RED);
//右旋转
rotateRight(parentOf(parentOf(x)));
}
// 如果 X 的父节点是其祖父节点的右节点
} else {
// 获取X 的父节点的兄弟节点即 叔叔节点
Entry<K,V> y = leftOf(parentOf(parentOf(x)));
// 只着色的情况对应的是最开始的例子,没有旋转操作,但要对应多次变换
//如果x的父节点的兄弟节点是红色
if (colorOf(y) == RED) {
//将 x 的父节点设置为黑色
setColor(parentOf(x), BLACK);
//将x的叔叔节点设置为黑色
setColor(y, BLACK);
//将x的祖父节点设置为红色
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {// 如果x 的叔叔节点是黑色
//如果x是父节点的左节点
if (x == leftOf(parentOf(x))) {
// 将x 的父节点设为x
x = parentOf(x);
rotateRight(x);
}
//将x的父节点设为黑色
setColor(parentOf(x), BLACK);
//把x的祖父节点设为红色
setColor(parentOf(parentOf(x)), RED);
rotateLeft(parentOf(parentOf(x)));
}
}
}
//将根节点强制设为黑色
root.color = BLACK;
}
TreeMap的插入节点和普通的排序二叉树的区别在于 TreeMap插入节点之后会调用方法
fixAfterInsertion(e)来重新调整红黑树的结构来让红黑树保持平衡
下面可以来看下fixAfterInsertino(e)方法的执行流程
第一种:只需变色就可保持平衡
在下面的这个树中插入节点51
当我们插入节点51之后,TreeMap的put方法执行后会得到下面的这张图
接着调用 fixAfterInsertion(e)方法,代码流程如下
当第一次进入循环之后,执行后会得到下面的红黑树结构
再把x重新赋值之后,重新进入while循环,此时x节点为45
执行上述流程后,得到下面所示的红黑树结构
这个时候 X被重新赋值为69,因为60是根节点,所以会退出while循环。在退出循环后,会再次把根节点设置为黑色,得到最终的结构如下图
最后经过两次执行while循环后,我们的红黑树会调整成现在这样的结构,这样的红黑树结构是平衡的,所以路径的黑高一致,并且没有红色节点相连的情况。
第二种: 旋转搭配变色来保持平衡
给定下面这样一颗红黑树
现在插入节点66,得到如下的树结构
之后进入fixAfterInsertion(e)方法
最终得到的红黑树的机构如下
这样红黑树就保持平衡了
红黑树的删除
删除的时候需要考虑这个节点位于哪个位置,是否有左右节点
删除节点是根节点
直接删除根节点即可
删除节点的左孩子和右孩子都为空
直接删除当前节点即可
删除节点有一个子节点不为空
这时候需要使用子节点来代替当前需要删除的节点,之后再把子节点删除即可
如下图,当我们需要删除节点69的时候
首先用子节点代替当前删除节点,然后再把子节点删除
最终的红黑树结构如下面所示,这个结构的红黑树我们是不需要通过变色+旋转来保持红黑树的平衡了,因为将子节点删除后树已经是平衡的了。
还有一种场景是当我们待删除节点是黑色的,黑色的节点被删除后,树的黑高就会出现不一致的情况,这个时候就需要重新调整结构。
还是拿上面这颗删除节点后的红黑树举例,我们现在需要删除节点67。
因为67 这个节点的两个子节点都是null,所以直接删除,得到如下图所示结构:
这个时候我们树的黑高是不一致的,左边黑高是3,右边是2,所以我们需要把64节点设置为红色来保持平衡。
删除节点的两个子节点都不为空
删除节点两个自及诶单都不为空的情况下。跟上面有一个节点不为空的情况也是有些类似。
同样是需要找到能代替当前节点的节点,找到后,把能替代删除节点值复制过来,然后再把替代节点删除掉
- 先找到替代节点,也就是前驱结点后者后继节点
- 然后再把前驱节点或者后继节点复制到当前待删除节点的位置,然后在删除前驱结点或者后继节点。
前驱节点:对一棵二叉树进行中序遍历,遍历后的顺序,当前节点的前一个节点为该节点的前驱节点;
后继节点:对一棵二叉树进行中序遍历,遍历后的顺序,当前节点的后一个节点为该节点的后继节点;
简单点:前驱是左子树中最大的节点,后继则是右子树中最小的节点。
如上图的的二叉树按照中序遍历为(43,45,49,56,58,60,64,66,68,72)那么 60的前驱结点是58后继节点是64
前驱或者后继都是最接近当前节点的节点,当我们需要删除当前节点的时候,也就是找到能替代当前节点的节点。
第一种,前驱结点为黑色节点,同时有一个非空节点
如下面一棵树,我们需要删除节点64
首先找到前驱结点,把前驱结点复制到当前节点
接着删除当前前驱结点
这个时候60和63这两个节点都是红色的,我们尝试把60这个节点设置为黑色即可使整个红黑树达到平衡。
第二种,前驱结点为黑色节点,同时子节点都为空
前驱结点是黑色的,子节点都为空,这个时候操作步骤与上面基本类似
操作步骤如下
因为要删除节点64,接着找到前驱节点63,把63节点复制到当前位置,然后将前驱节点63删除掉,变色后出现黑高不一致的情况下,最后把63节点设置为黑色,把65节点设置为红色,这样就能保证红黑树的平衡。
第三种,前驱节点为红色节点,同时子节点都为空
给定下面这颗红黑树,我们需要删除节点64的时候。
同样的,我们知道64的前驱结点63,接着把63赋值到64这个位置
然后删除前驱结点
删除节点后不需要变色也不需要旋转即可保持树的平衡。
public V remove(Object key) {
//这个是获取这个key对应的entry
Entry<K,V> p = getEntry(key);
if (p == null)
return null;
V oldValue = p.value;
//删除这个节点,
deleteEntry(p);
return oldValue;
}
deleteEntry(e)可以删除一个节点,并且保持树的平衡
private void deleteEntry(Entry<K,V> p) {
//记录操作数
modCount++;
// 二叉树里面节点数量减一
size--;
// If strictly internal, copy successor's element to p and then make p
// point to successor.
//目标节点的左右子树不为空
if (p.left != null && p.right != null) {
//获取后继节点
Entry<K,V> s = successor(p);
p.key = s.key;
p.value = s.value;
p = s;
} // p has 2 children
// Start fixup at replacement node, if it exists.
//要删除的节点只有一个子节点为空
Entry<K,V> replacement = (p.left != null ? p.left : p.right);
if (replacement != null) {
// Link replacement to parent
replacement.parent = p.parent;
//如果p是根节点
if (p.parent == null)
root = replacement;
else if (p == p.parent.left)
p.parent.left = replacement;
else
p.parent.right = replacement;
// Null out links so they are OK to use by fixAfterDeletion.
//将目标节点的连接都清空
p.left = p.right = p.parent = null;
// Fix replacement
if (p.color == BLACK)
//调整
fixAfterDeletion(replacement);
} else if (p.parent == null) { // return if we are the only node.
root = null;
} else { // No children. Use self as phantom replacement and unlink.
//目标节点的左右子树都为空
if (p.color == BLACK)
fixAfterDeletion(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;
}
}
}
successor() 寻找节点的后继函数
static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
if (t == null)
return null;
// t.右子树不空,则t的后继节点是其右子树中最小的那个元素
else if (t.right != null) {
Entry<K,V> p = t.right;
while (p.left != null)
p = p.left;
return p;
} else {
// t 的右孩子为空,则t的后继是其第一个向左走的祖先
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() 调整位置、颜色
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));
x = root;
}
} else { // symmetric
Entry<K,V> sib = leftOf(parentOf(x));
if (colorOf(sib) == RED) {
setColor(sib, BLACK);
setColor(parentOf(x), RED);
rotateRight(parentOf(x));
sib = leftOf(parentOf(x));
}
if (colorOf(rightOf(sib)) == BLACK &&
colorOf(leftOf(sib)) == BLACK) {
setColor(sib, RED);
x = parentOf(x);
} else {
if (colorOf(leftOf(sib)) == BLACK) {
setColor(rightOf(sib), BLACK);
setColor(sib, RED);
rotateLeft(sib);
sib = leftOf(parentOf(x));
}
setColor(sib, colorOf(parentOf(x)));
setColor(parentOf(x), BLACK);
setColor(leftOf(sib), BLACK);
rotateRight(parentOf(x));
x = root;
}
}
}
setColor(x, BLACK);
}
左转、右转
/** From CLR */
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;
}
}
/** From CLR */
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;
}
}