NIL
),叶子节点不存储数据。注意:
Binary Search Tree
,简称BST
)是一棵二叉树,它的左子节点的值比父节点的值要小,右节点的值要比父节点的值大。它的高度决定了它的查找效率。二叉搜索树:
平衡二叉树:
平衡二叉树AVL
:
红黑树:
AVL
去除红黑树中的红色节点:
红色节点删除之后,有些节点就没有父节点了,它们会直接拿这些节点的祖父节点(父节点的父节点)作为父节点。所以,之前的二叉树就变成了四叉树。
从四叉树中取出某些节点,放到叶节点位置,四叉树就变成了完全二叉树。所以,仅包含黑色节点的四叉树的高度,比包含相同节点个数的完全二叉树的高度还要小。
完全二叉树的高度近似 l o g 2 N log_{2}{N} log2N,这里的四叉“黑树”的高度要低于完全二叉树,所以去掉红色节点的“黑树”的高度也不会超过 l o g 2 N log_{2}{N} log2N。
假设某条查询路径上的红色节点为N
个,则黑色节点之至少有N
个(根节点必须为黑色节点)
最坏情况下红黑树的高度只比高度平衡的 AVL
树的高度仅仅大了一倍,在性能上,下降得并不多。推导出来的结果不够精确,实际上红黑树的性能更好。
TreeMap
的源码/* 获取节点颜色 */
private static <K,V> boolean colorOf(Entry<K,V> p) {
return (p == null ? BLACK : p.color);
}
/* 设置节点颜色 */
private static <K,V> void setColor(Entry<K,V> p, boolean c) {
if (p != null)
p.color = c;
}
/* 获取节点的父节点 */
private static <K,V> Entry<K,V> parentOf(Entry<K,V> p) {
return (p == null ? null: p.parent);
}
/* 获取节点的左子节点 */
private static <K,V> Entry<K,V> leftOf(Entry<K,V> p) {
return (p == null) ? null: p.left;
}
/* 获取节点的右子节点 */
private static <K,V> Entry<K,V> rightOf(Entry<K,V> p) {
return (p == null) ? null: p.right;
}
/* 通过自定义比较器或内置比较器比较元素大小 */
final int compare(Object k1, Object k2) {
return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
: comparator.compare((K)k1, (K)k2);
}
a / b / r
可以为子树、元素、空元素NIL
p
的转动TreeMap
源码
private void rotateLeft(Entry<K,V> p) {
if (p != null) {
Entry<K,V> r = p.right;
/* 将 r 的左子节点 交给 p 的右子节点 */
p.right = r.left;
if (r.left != null)
r.left.parent = p;
/* 将 r 和 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;
/* p 变成 r 的左子节点*/
r.left = p;
p.parent = r;
}
}
TreeMap
源码
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;
}
}
红黑树的平衡调整过程是一个向root节点回溯迭代的过程,当关注节点符合红黑树定义或到达根节点,平衡调整结束
关注节点:正在处理的节点。关注节点会随着不停地迭代处理,而不断发生变化
本文以左子树举例,右子树为镜像操作
红黑树规定,插入的节点必须是红色的。而且,二叉查找树中新插入的节点都是放在叶子节点上。
为了简化描述,把父节点的兄弟节点叫作叔叔节点,父节点的父节点叫作祖父节点。
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;
}
a / b / r
可以为子树、元素、空元素NIL
处理方法:
更改叔、父颜色为黑色、祖父颜色为红色(满足条件三:任何相邻的节点都不能同时为红色。)
同时,在以祖父节点为根节点的子树中,所有叶子节点到达祖父节点经过的黑色节点数不变(满足条件四:任何一个节点向下遍历到其子孙的叶子节点,所经过的黑节点个数必须相等。)
由于对祖父节点进行了改色处理,但又不知道祖父节点的父节点是否为红色(不一定满足条件三)
仅截取左子节点情况下的处理方法
/* 迭代终止条件 */
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));
}
}
}
处理方法:
更改父节点颜色为黑色,祖父节点颜色为红色(满足条件三:任何相邻的节点都不能同时为红色。)
对祖父节点执行右旋操作,提升父亲节点,将父亲节点的右子节点变成祖父节点的左子节点,旋转后,各叶子节点到达当前子树根节点经过的黑色节点数不变(满足条件四:任何一个节点向下遍历到其子孙的叶子节点,所经过的黑节点个数必须相等。)
操作结束后,不存在不满足条件的节点,迭代结束
TreeMap
中相关处理代码
处理方法:
TreeMap
中相关处理代码
/* 迭代终止条件 */
while (x != null && x != root && x.parent.color == RED) {
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
} else { /* 父节点是祖父节点的右子节点 */
Entry<K,V> y = leftOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
} else { /* 叔 节点的颜色不为红色 */
/* 情况二:如果不共线,则进行一次旋转操作,并更改关注节点 */
if (x == leftOf(parentOf(x))) {
x = parentOf(x);
rotateRight(x);
}
/* 情况二:共线的祖父-父亲-关注节点 旋转祖父节点,执行颜色改变*/
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateLeft(parentOf(parentOf(x)));
}
}
}
删除操作首先需要做的也是BST
的删除操作,删除操作会删除对应的节点
红黑树的平衡调整过程是一个向root节点回溯迭代的过程,当关注节点符合红黑树定义或到达根节点,平衡调整结束
区分:
关注节点r
:正在处理的节点。关注节点会随着不停地迭代处理,而不断发生变化
待删除节点p
:删除操作执行前,节点中key
与要删除的key
值相等的节点
删除操作开始时,初始关注节点的位置与具体情况有关,并不与待删除节点恒相等
注意:
本文以左子树举例,右子树为镜像操作
为了简化描述,把父节点的兄弟节点叫作叔叔节点,父节点的父节点叫作祖父节点。
getEntry(key)
函数,找待删除节点的位置P
节点,将r
节点设置为关注节点,进入平衡调整操作TreeMap
中相关处理代码
private void deleteEntry(Entry<K,V> p) {
/* 如果左右子节点都存在,需要特殊处理 */
if (p.left != null && p.right != null) {
}
/* r 指针指向待删除节点p的唯一子节点 */
Entry<K,V> replacement = (p.left != null ? p.left : p.right);
/* 确实存在唯一子节点 */
if (replacement != null) {
/* 执行删除 p 节点操作 */
replacement.parent = p.parent;
if (p.parent == null)
root = replacement;
else if (p == p.parent.left)
p.parent.left = replacement;
else
p.parent.right = replacement;
/* 确保不会影响GC回收 */
p.left = p.right = p.parent = null;
/* 进入平衡调整的条件,后续会讲 */
if (p.color == BLACK)
fixAfterDeletion(replacement);
}
}
使用待删除元素中序遍历的后续元素的key & value
值更改待删除元素节点,不改变树的结构,仅做值的更改
后继元素一定没有左子节点,否则其不是待删除元素中序遍历的后继节点(可证明)
将后继元素标记为待删除元素,在基础删除操作中重新匹配情况
TreeMap
中相关处理代码
private void deleteEntry(Entry<K,V> p) {
/* 当待删除元素的左右子树都存在时,找到其后继元素,替换值,重新标记为待删除元素 */
if (p.left != null && p.right != null) {
Entry<K,V> s = successor(p);
p.key = s.key;
p.value = s.value;
p = s;
}
}
private void deleteEntry(Entry<K,V> p) {
/* 定位到待删除元素一个非空的子节点 */
Entry<K,V> replacement = (p.left != null ? p.left : p.right);
if (replacement != null) {
} else if (p.parent == null) {
}
/* 左右子节点均为空,说明为叶子节点 */
else {
/* 删除前需要执行平衡调整操作 */
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;
}
}
}
TreeMap
中相关处理代码
private void deleteEntry(Entry<K,V> p) {
/* 如果左右子节点都存在,需要特殊处理 */
if (p.left != null && p.right != null) {
}
/* r 指针指向待删除节点p的唯一子节点 */
Entry<K,V> replacement = (p.left != null ? p.left : p.right);
/* 确实存在唯一子节点 */
if (replacement != null) {
}
/* 待删除节点为根节点 */
else if (p.parent == null) {
root = null;
} else {
}
}
平衡调整迭代的终止条件(修复完成):
关注节点是root
节点时
关注节点是红色节点
关注节点的含义(个人理解):
黑色路径长度:我们定义,在一个红黑树中,叶子节点到其根节点经过的黑色节点数为
以图为例,待删除节点为2
节点
NIL
的黑色路径长度均为26
更改为红色,使树中每个叶子节点的黑色路径长度均为1在以关注节点为根节点的子树中,所有的叶子节点的黑色路径长度缺少1(相较于关注节点的兄弟节点)
平衡调整:解决上述问题,在删除元素后,解决关注节点为根节点的子树中,叶子结点的黑色路径长度少1的问题
b / r
可以为子树、元素、空元素NIL
处理方法:
TreeMap
中相关处理代码
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) {
} else {
}
}
}
/* 设置根节点,或者颜色为红色的关注节点为黑色 */
setColor(x, BLACK);
}
b / r
可以为子树、元素、空元素NIL
处理方法:
TreeMap
中相关处理代码
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) {
}
/* 兄弟及其子节点均为黑色节点 */
if (colorOf(leftOf(sib)) == BLACK &&
colorOf(rightOf(sib)) == BLACK) {
setColor(sib, RED);
/* 调整关注节点,进入迭代 */
x = parentOf(x);
}
}
}
setColor(x, BLACK);
}
b / r
可以为子树、元素、空元素NIL
处理方法:
TreeMap
中相关处理代码
b / r
可以为子树、元素、空元素NIL
处理方法:
TreeMap
中相关处理代码
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(leftOf(sib)) == BLACK &&colorOf(rightOf(sib)) == BLACK) {
}
/* 子节点中一定存在一个红色节点 */
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;
}
}
}
setColor(x, BLACK);
}
对红黑树的认识总结_张彦峰ZYF的博客-CSDN博客
红黑树深入剖析及Java实现 - 美团技术团队 (meituan.com)
红黑树详解_晓之木初的博客-CSDN博客_红黑树