我正在参加「掘金·启航计划」
TreeMap
TreeMap的内部结构是一棵红黑树,查找,添加 和删除的效率都是log(n);且内部所存储的元素都是有序的,为了使TreeMap中的元素是有序的,必须要提供一个能够比较TreeMap内部存储的元素的方法;TreeMap提供了两种方式来帮助TreeMap比较内部所存储的元素;
方式一:在创建TreeMap时,在构造方法中传入一个比较器Comparator
方式二:让被存入TreeMap的元素的键实现Comparable接口。
TreeMap的继承结构
- AbstractMap 实现了一些Map中的基本方法,减少了Map的实现类的工作量。
- NavigableMap 提供了一些返回TreeMap中与给定键值最接近的元素的方法。
- Serializable 序列化接口,对象持久化的方法。
- Cloneable 标记接口,表示对象可以被clone。
字段
// 用来确定TreeMap中元素的顺序的比较器,如果为null,就调用被存储键的CompareTo方法。
private final Comparator<? super K> comparator;
// root根节点
private transient Entry<K,V> root;
// TreeMap中元素的个数
private transient int size = 0;
// TreeMap的修改次数。
private transient int modCount = 0;
构造方法
//创建一棵空树
public TreeMap() {
comparator = null;
}
// 创建一棵带有比较器的空树,向本TreeMap中添加元素都会都会按照这个比较器确定顺序。如果为null,就调用被存储键的CompareTo方法。
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
// 根据传入的Map m构造一个包含m中所有元素新的map
public TreeMap(Map<? extends K, ? extends V> m) {
comparator = null;
putAll(m);
}
// 根据传入的SortedMap m构造一个包含m中所有元素的新map,因为m是已排好序的map,
//所以在向新map中拷贝元素时,不用再反复确认元素的顺序。可以提高创建新map的效率
public TreeMap(SortedMap<K, ? extends V> m) {
comparator = m.comparator();
try {
buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
} catch (java.io.IOException cannotHappen) {
} catch (ClassNotFoundException cannotHappen) {
}
}
常用方法
containsValue
整体思路:从最左侧节点开始,通过不断寻找后继节点遍历整棵红黑树,如果遍历完整棵红黑树没有找到指定的值,就返回false。
// 判断TreeMap中有没有与value相同的值
public boolean containsValue(Object value) {
// 从最左边的元素开始遍历整棵树,如果树中有键值对的值与value相等,就返回true。
for (Entry<K,V> e = getFirstEntry(); e != null; e = successor(e))
if (valEquals(value, e.value))
return true;
return false;
}
// 返回树中第一个键值对,也就是最左侧的键值对
final Entry<K,V> getFirstEntry() {
Entry<K,V> p = root; //从root开始
if (p != null)
while (p.left != null) //一路向左
p = p.left;
return p;
}
// 返回指定节点的后继节点
static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
if (t == null) //t是null的话,就没有后继节点,返回null。
return null;
else if (t.right != null) { // 如果节点t有右子树的话,那么t的后继节点为,右子树最左侧的元素。
Entry<K,V> p = t.right;
while (p.left != null)
p = p.left;
return p;
} else {// 如果节点t没有右子树,
// 1. 如果t是父节点的左子节点,直接返回父节点
Entry<K,V> p = t.parent;
Entry<K,V> ch = t;
// 2. 如果t是父节点的右子节点,需要一直向上。直到ch是父亲节点的左子节点。
while (p != null && ch == p.right) {
ch = p;
p = p.parent;
}
return p;
}
}
// 判断o1,o2是否相等
static final boolean valEquals(Object o1, Object o2) {
// 增加了判空操作,防止出现空指针异常,o1,o2同时为null 返回true。
return (o1==null ? o2==null : o1.equals(o2));
}
get
整体思路:TreeMap是依靠Comparator的compare方法,或key实现Comparable接口中的compareTo方法 来确定元素的位置。
TreeMap优先Comparator的compare方法,如果没有比较器Comparator,再使用key的compareTo方法 来寻找元素。
// 通过键 获取元素值
public V get(Object key) {
Entry<K,V> p = getEntry(key);
// p等于null 就返回null,否则就返回元素值。
return (p==null ? null : p.value);
}
// 通过键获取元素
final Entry<K,V> getEntry(Object key) {
// 如果比较器不为空,就调用getEntryUsingComparator,使用比较器判断元素位置。否则就用键的compareTo方法。
if (comparator != null)
return getEntryUsingComparator(key);
// 如果键为空,就抛出空指针异常
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
// 将键强制转换为Comparable类型,如果键k没有实现Comparable接口,就会抛出异常。
Comparable<? super K> k = (Comparable<? super K>) key;
// 从根节点开始
Entry<K,V> p = root;
while (p != null) {
int cmp = k.compareTo(p.key);
if (cmp < 0)// 如果cmp>0, 说明目标key在当前节点的左侧
p = p.left;
else if (cmp > 0)// 如果cmp>0, 说明目标key在当前节点的右侧。
p = p.right;
else // 如果cmp=0,说明当前节点就是要找的节点
return p;
}
return null;
}
// 用比较器来查找元素位置
final Entry<K,V> getEntryUsingComparator(Object key) {
@SuppressWarnings("unchecked")
K k = (K) key;
Comparator<? super K> cpr = comparator;
if (cpr != null) {
Entry<K,V> p = root;
while (p != null) {
int cmp = cpr.compare(k, p.key);
if (cmp < 0)// 如果cmp>0, 说明目标key在当前节点的右侧。
p = p.left;
else if (cmp > 0)// 如果cmp>0, 说明目标key在当前节点的右侧。
p = p.right;
else // 如果cmp=0,说明当前节点就是要找的节点
return p;
}
}
return null; //mei'zhao'd
}
put
整体思路是:如果根节点为空,就把新节点直接放在根结点上。
如果根节点不为空,再根据有无比较器,调用不同的比较方法,找到被插入节点的父节点位置。 最后插入新节点。
// 向TreeMap中存放键值对
public V put(K key, V value) {
Entry<K,V> t = root; //从根节点开始,寻找键值对存放的位置。
if (t == null) {
// 检查key是否可以比较。
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null); // 如果根节点为null,直接将新创建的节点赋值给root
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
// 如果有比较器,就采用比较器比较,没有的话,就采用compareTo方法比较。
if (cpr != null) {
// 循环查找查入元素的父节点位置
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0) // 如果cmp<0,说明目标key在当前节点的左侧。
t = t.left;
else if (cmp > 0) // 如果cmp>0,说明目标key在当前节点的右侧。
t = t.right;
else // 如果cmp=0,说明当前节点的key,就是目标key。 目标key在TreeMap中已经存在,只需要更新当前节点的value即可。
return t.setValue(value);
} while (t != null);
}
else { // 使用key的compareTo方法比较
if (key == null) // 不能出现空键
throw new NullPointerException();
@SuppressWarnings("unchecked")
// 将key强制转换为Comparable类型,如果key没有实现Comparable接口,就会报错
Comparable<? super K> k = (Comparable<? super K>) key;
// 循环遍历搜索插入元素的父节点位置
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0) // 如果cmp<0,说明目标key在当前节点的左侧。
t = t.left;
else if (cmp > 0) // 如果cmp>0,说明目标key在当前节点的右侧。
t = t.right;
else // 如果cmp=0,说明当前节点的key,就是目标key。 目标key在TreeMap中已经存在,只需要更新当前节点的value即可。
return t.setValue(value);
} while (t != null);
}
// 根据key和value创建新节点
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0) // 如果cmp<0, 把新节点连接在父节点的左侧
parent.left = e;
else // 如果cmp>0, 把新节点连接在父节点的右侧
parent.right = e;
// 修复插入新节点后的红黑树
fixAfterInsertion(e);
// 元素个数+1
size++;
// 操作数+1;
modCount++;
// 如果是插入元素,就返回null,如果是替换元素,就返回被替换的值
return null;
}
putAll
整体思路:基于有序map的特性,TreeMap中提供了buildFromSorted方法,用于从一个有序的SortedMap中向本TreeMap中添加多个元素, 但要求本TreeMap必须是空树。
如果TreeMap是空树,就调用buildFromSorted方法,buildFromSorted方法的效率更高,否则就调用AbstractMap中putAll方法,AbstractMap中putAll方法就是反复调用put方法,效率比较低。
buildFromSorted方法,采用递归的方式创建红黑树,每次递归都会创建一个位于指定范围内的最中间的节点。并且再次递归创建左子树和右子树。
buildFromSorted方法还将位于最底层的元素全部染为红色,其他节点都是黑色,这样做的目的是,防止最后一层的元素未满,导致黑高不平衡。
// 将参数map中的元素全部添加到本TreeMap中
public void putAll(Map<? extends K, ? extends V> map) {
// 参数map中的元素个数
int mapSize = map.size();
// buildFromSorted是一个专门针对从SortedMap向本TreeMap中添加元素而设计的方法,它比AbstractMap中的putAll方法更加高效。
// 本TreeMap必须是空树,而且参数map必须是SortedMap的子类,才会调用buildFromSorted方法。否则就调用AbstractMap中的putAll方法
if (size==0 && mapSize!=0 && map instanceof SortedMap) {
Comparator<?> c = ((SortedMap<?,?>)map).comparator();
// 若参数map中有比较器Comparator,则本TreeMap中的比较器必须与参数map中的比较器相同,要么都为空。否则无法执行添加操作。
if (c == comparator || (c != null && c.equals(comparator))) {
// 操作数++
++modCount;
try {
// 参数:mapSize map的大小,map.entrySet().iterator() map的迭代器。
buildFromSorted(mapSize, map.entrySet().iterator(),
null, null);
} catch (java.io.IOException cannotHappen) {
} catch (ClassNotFoundException cannotHappen) {
}
return;
}
}
// 使用AbstractMap中的putAll方法,就是反复调用put方法
super.putAll(map);
}
// 从SortedMap中向本集合中添加元素的方法。
private void buildFromSorted(int size, Iterator<?> it,
java.io.ObjectInputStream str,
V defaultVal)
throws java.io.IOException, ClassNotFoundException {
// 修改本TreeMap的元素个数。
this.size = size;
// 真正构造红黑树的方法,下面再展开讲。
root = buildFromSorted(0, 0, size-1, computeRedLevel(size),
it, str, defaultVal);
}
// 计算出由sz个元素组成的红黑树有几层,最后被创建出来的红黑树最后一层全部要染成红色。
private static int computeRedLevel(int sz) {
int level = 0;
for (int m = sz - 1; m >= 0; m = m / 2 - 1)
level++;
return level;
}
/**
* int level: 当前构造的节点是红黑树的第几层。
* int lo, int hi 从map的下标lo~hi的位置,构造一棵子树。
* int redLevel 红色节点所在的层。
* Itertor<?> it map的迭代器。
* java.io.ObjectInputStream str 对象输出流。
* V defaultVal 默认值
* 构造策略:
* 它会将迭代器中最中间的元素作为根节点 并返回。并且递归地创建左子树 和 右子树。
*/
private final Entry<K,V> buildFromSorted(int level, int lo, int hi,
int redLevel,
Iterator<?> it,
java.io.ObjectInputStream str,
V defaultVal)
throws java.io.IOException, ClassNotFoundException {
// 如果高位下标大于低位下标 就返回null。
if (hi < lo) return null
// 计算位于中间元素的下标。无符号右移,相当于整除2;
int mid = (lo + hi) >>> 1
Entry<K,V> left = null;
// 递归地创建左子树
if (lo < mid)
left = buildFromSorted(level+1, lo, mid - 1, redLevel,
it, str, defaultVal
// extract key and/or value from iterator or stream
K key;
V value;
// 如果迭代器不为空,就使用迭代器获取键值对,如果迭代器为空,就使用对象输出流获取键值对
if (it != null) {
// 判断参数defaultVal是否为空,空的话使用迭代器中的键值对的值,不为空的话,使用defaultVal作为新创建节点的值。
if (defaultVal==null) {
// 迭代器获取键值对对象
Map.Entry<?,?> entry = (Map.Entry<?,?>)it.next();
key = (K)entry.getKey();
value = (V)entry.getValue();
} else {
key = (K)it.next();
// 使用默认值作为新节点的值
value = defaultVal;
}
} else { // use stream
// 从对象输入流中获取对象。
key = (K) str.readObject();
value = (defaultVal != null ? defaultVal : (V) str.readObject());
}
// 创建节点。
Entry<K,V> middle = new Entry<>(key, value, null);
// color nodes in non-full bottommost level red
// 将最底层的节点染为红色,这也做是为了保持黑高。
if (level == redLevel)
middle.color = RED;
// 连接左子树
if (left != null) {
middle.left = left;
left.parent = middle;
// 递归创建右子树
if (mid < hi) {
Entry<K,V> right = buildFromSorted(level+1, mid+1, hi, redLevel,
it, str, defaultVal);
middle.right = right;
right.parent = middle;
// 返回本层递归创建的节点。
return middle;
}
remove
整体思路:先调用getEntry方法,判断该键值对是否存在。如果存在再调用deleteEntry方法进行删除,删除时,若被删除节点不是叶子节点,就找到被删除节点的后继节点,交换删除节点与被删除节点的键和值,再删除后继节点代替删除被删除节点。
//移除指定节点,并返回被移除节点的值
public V remove(Object key) {
// 先判断TreeMap中是否存在该key。不存在就直接返回null
Entry<K,V> p = getEntry(key);
if (p == null)
return null;
// 存在的话,就直接删除指定节点,再返回被删除节点的元素值
V oldValue = p.value;
deleteEntry(p);
return oldValue;
}
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.
// p点被删除后,replacement将取代p节点的位置。replacement为p的左子节点或右子节点
Entry<K,V> replacement = (p.left != null ? p.left : p.right);
// 如果replacement不为null,就将replacement连接到p的位置,如果replacement为null,就直接将连接到p的引用都置为null。
if (replacement != null) {
// 修改replacement的parent
replacement.parent = p.parent;
// 如果p的parent为null,说明p是根节点,直接将replacement赋值给root。
if (p.parent == null)
root = replacement;
// 将原本指向p的引用修改为指向replacement
else if (p == p.parent.left)
p.parent.left = replacement;
else
p.parent.right = replacement;
// 将p内部的引用变量都置空。
p.left = p.right = p.parent = null;
// 红色节点不用修复,如果是黑色节点就需要啊修复
if (p.color == BLACK)
fixAfterDeletion(replacement);
} else if (p.parent == null) { // 如果p是TreeMap中唯一一个元素,直接将root=null即可。
root = null;
} else { // 如果p是叶子节点,就先修复,再删除这个叶子节点
// 如果p是黑色,就需要修复红黑树。
if (p.color == BLACK)
fixAfterDeletion(p);
// 将指向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;
}
}
}
rotateLeft 左旋
左旋、右旋 和 变色是红黑树保持性质的三种基本操作。
我在上一篇博客中已经梳理了红黑树插入 和删除的不同情况下的处理方法,跟官方源码的处理方法也大致相同。只不过官方源码更加简洁,易懂;所以再来学习一下。
/**
* p p
* | |
* n y
* / \ ---> / \
* x y n ry
* / \ / \
* ly ry x ly
* 左旋操作
* 1. 将y的左子节点ly连接到n右子节点上
* 2. 修改ly的父节点指向n
* 3. 将n连接到y的左子节点位置上
* 4. 修改n的parent指定y
* 5. 修改y的parent指向n的父节点
* 6. 修改p本指向n的子节点指向y
*/
// 左旋
private void rotateLeft(Entry<K,V> p) {
// 判空 避免空指针。
if (p != null) {
// r为p的右子节点 左旋后,r将成为p的父亲节点。p将成为r的左子节点。
Entry<K,V> r = p.right;
// 将r的左子节点连接到p的右子节点上。
p.right = r.left;
// 同时要修改r的左节点的父亲节点。
if (r.left != null)
r.left.parent = p;
// 修改r的parent 为 p的parent
r.parent = p.parent;
// 如果p的parent为null,直接将root改为r。
if (p.parent == null)
root = r;
else if (p.parent.left == p) // 如果p的parent不为null,修改原p的父亲节点指向p的指针修改为指向r。
p.parent.left = r;
else
p.parent.right = r;
r.left = p; // p成为r的左子节点
p.parent = r;
}
}
rotateRight 右旋
/**
* p p
* | |
* n x
* / \ / \
* x y ---> lx n
* / \ / \
* lx rx rx y
*
*
* 右旋操作
* 1. 将x的右子节点rx连接到n左子节点上
* 2. 修改rx的父节点指向n
* 3. 将n连接到x的右子节点位置上
* 4. 修改n的parent指定x
* 5. 修改x的parent指向n的父节点
* 6. 修改p本指向n的子节点指向x
*/
private void rotateRight(Entry<K,V> p) {
// 判空 避免空指针异常
if (p != null) {
// l为p的左子节点 右旋后,p的左子节点l将成为p的父亲节点,p将成为l的右子节点。
Entry<K,V> l = p.left;
// 将l的右子节点连接到p的左子节点上。
p.left = l.right;
// 同时修改l的右子节点的parent指针。
if (l.right != null)
l.right.parent = p;
// 修改l的parent 指向p的parent。
l.parent = p.parent;
// 如果p的parent为null,直接将l赋值给root
if (p.parent == null)
root = l;
else if (p.parent.right == p) // 如果p的parent不为null,修改原p的父亲节点指向p的指针修改为指向r。
p.parent.right = l;
else p.parent.left = l;
l.right = p; // p成为l的右子节点。
p.parent = l;
}
}
fixAfterInsertion
修复因节点操作导致的红黑树不平衡。
插入节点导致的问题情况有以下几种情况。
- root为空,直接将root指向新节点。
- 父亲节点是黑色,直接插入,无影响。
- 键值已存在,更新键值对的值。
- 如果父节点是红色,因为插入的节点是红色,红红不能相连,就需要处理 4.1. 叔叔节点为红色 将父亲节点和叔叔节点染黑。爷爷节点染红。 4.2. 叔叔结点为黑色或为空 4.2.1. 父节点在爷爷节点左侧,子节点在父节点左侧(LL双红) 将父亲节点染黑。爷爷节点染红。对爷爷节点右旋。 4.2.2. 父节点在爷爷节点左侧,子节点在父节点右侧(LR双红) 对父亲节点左旋 然后转-->4.2.1. 4.2.3. 父节点在爷爷节点右侧,子节点在父节点右侧(RR双红) 将父亲节点染黑。爷爷节点染红。对爷爷节点左旋。 4.2.4. 父节点在爷爷节点右侧,子节点在父节点左侧(RL双红)、 对父亲节点右旋 然后转-->4.2.3.
可以看到,前三种情况在插入节点阶段就能解决。需要修复的只有情况4
private void fixAfterInsertion(Entry<K,V> x) {
// 被插入节点默认都是红色。
x.color = RED;
// 如果x的父节点是红色,就需要修复,如果修复一直上溢到根节点root,就得退出,因为无法继续向上修复了。
while (x != null && x != root && x.parent.color == RED) {
// 父亲节点是祖父节点的左子节点
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
// y是被插入节点的叔叔节点。
Entry<K,V> y = rightOf(parentOf(parentOf(x)));
// 叔叔节点是红色 4.1 的情况
if (colorOf(y) == RED) {
// 将父亲节点染黑
setColor(parentOf(x), BLACK);
// 叔叔节点染黑
setColor(y, BLACK);
// 祖父节点染红
setColor(parentOf(parentOf(x)), RED);
// 再将祖父节点当作被插入节点继续向上修复。
x = parentOf(parentOf(x));
} else {
//4.2.2父节点在爷爷节点左侧,被插入节点在父节点右侧
if (x == rightOf(parentOf(x))) {
// 这里需要对父亲节点左旋,但是左旋后,原来的父亲节点就成为了当前节点的左子节点。所以这里直接将当前节点x的父亲节点赋值给x,再对x左旋,把x当成被插入节点。这时情况就转变成了4.2.1.
x = parentOf(x);
rotateLeft(x);
}
// 4.2.1. 父节点在爷爷节点左侧,被插入节点在父节点左侧
// 将父亲节点染为黑色。
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;
}
fixAfterDeletion
修复因删除节点造成的红黑树不平衡。
删除节点造成的情况如下所示。
- 被删除节点是红色 这种情况不影响红黑树的性质,可以直接删除
- 被删除节点是黑色 2.1. 兄弟节点是黑色,且兄弟节点有红色的子节点 2.1.1. 兄弟节点的左子节点是红色 此时父亲节点的右子树为空,左子树为ll(兄弟节点在父亲节点的左侧,兄弟节点的左子节点在兄弟节点的左侧 left-left), 调整方法:将兄弟节点的颜色染为原来父亲节点的颜色,再将兄弟节点的左子节点和父亲节点染为黑色, 再对父亲节点右旋。 这样此子树的黑高就与删除前的黑高相等,此子树与删除前的子树相比,少了一个红色节点。 2.1.2. 兄弟节点的右子节点是红色 此时父亲节点的右子树为空,左子树为lR(left-right) 对兄弟节点进行左旋,并交换兄弟节点与兄弟节点的右子节点的颜色,就可转换为2.1.1 的情况 2.2.兄弟节点是黑色,且兄弟节点无子节点 2.2.1. 父节点是红色 将父亲节点染为黑色,兄弟节点染为红色即可 2.2.2. 父节点是黑色 这种情况下,被删除节点、父亲节点、和兄弟节点都是黑色,被删除节点被删除后,不能在以父节点为根节点的范围内,保持黑高不变, 就需要扩大调整的范围。 具体操作为:将兄弟节点染为红色(以父节点为根节点的子树黑高减一),再以父亲节点为删除节点,修复红黑树。 2.3. 兄弟节点是红色(父节点必定为黑色(性质4),且必定有黑色子节点,否则黑高不平衡(性质5)) 父亲节点染为红色,兄弟节点染为黑色,再对父亲节点右旋,就转变成了其他情况。
private void fixAfterDeletion(Entry<K,V> x) {
// 被删除节点是黑色就需要修复,因为影响黑高。
while (x != root && colorOf(x) == BLACK) {
// 被删除节点x是父亲节点的左子节点
if (x == leftOf(parentOf(x))) {
// sib是被删除节点的兄弟节点。
Entry<K,V> sib = rightOf(parentOf(x));
// 2.3. 兄弟节点是红色
if (colorOf(sib) == RED) {
// 将兄弟节点染为黑色。
setColor(sib, BLACK);
// 父亲节点染为红色
setColor(parentOf(x), RED);
// 对父亲节点左旋
rotateLeft(parentOf(x));
// 更新兄弟节点 sib, 转变成其他情况继续修复。
sib = rightOf(parentOf(x));
}
// 2.2.兄弟节点是黑色,且兄弟节点无子节点。 空节点也是黑色节点。
// 2.2.1 和 2.2.2 的情况可以合并。
if (colorOf(leftOf(sib)) == BLACK &&
colorOf(rightOf(sib)) == BLACK) {
// 将兄弟节点染为红色
setColor(sib, RED);
// 将父节点赋值给x,继续向上修复红黑树
x = parentOf(x);
} else {
// 2.1.2.兄弟节点是黑色, 兄弟节点的右子节点是红色
if (colorOf(rightOf(sib)) == BLACK) {
// 将兄弟节点的左子节点染为黑色。
setColor(leftOf(sib), BLACK);
// 兄弟节点染为红色。
setColor(sib, RED);
// 对兄弟节点右旋。
rotateRight(sib);
// 更新兄弟节点sib,情况就转变成了2.1.1
sib = rightOf(parentOf(x));
}
// 2.1.1. 兄弟节点是黑色, 兄弟节点的左子节点是红色.
// 把兄弟节点的颜色染为父亲节点的颜色。
setColor(sib, colorOf(parentOf(x)));
// 父亲节点染为黑色。
setColor(parentOf(x), BLACK);
// 将兄弟节点染为黑色。
setColor(rightOf(sib), BLACK);
// 对父节点左旋
rotateLeft(parentOf(x));
// 结束循环。
x = root;
}
} else { // 跟上面一样,是对称的。
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);
}
\