(关于左旋,右旋,还有具体的图解,举例和细节。推荐一篇博客
史上最清晰的红黑树讲解(上) – CarpenterLee – 博客园 https://www.cnblogs.com/CarpenterLee/p/5503882.html)
这是我看完这篇博客之后自己的理解吧
红黑树是一种近似平衡的二叉查找树,它能够确保任何一个节点的左右子树的高度差不会超过二者中较低那个的一倍。
具体来说,红黑树是满足如下条件的二叉查找树(binary search tree):
1,每个节点要么是红色,要么是黑色。
2,根节点必须是黑色
3,红色节点不能连续(也即是,红色节点的孩子和父亲都不能是红色)。
4,对于每个节点,从该点至叶子节点的任何路径,都含有相同个数的黑色节点。
在树的结构发生改变时(插入或者删除操作),往往会破坏上述条件3或条件4,需要通过调整使得查找树重新满足红黑树的条件。
TreeMap的底层数据结构就采用的是红黑树。
TreeMap的get方法其实是调用了getEntry(Object key)方法。将要查找的键值与当前节点的键值进行比较,如果大于当前节点的key,则向当前节点的右子树进行查找,小于则向左子树进行查找。
public V get(Object key) {
Entry<K,V> p = getEntry(key);
return (p==null ? null : p.value);
}
final Entry<K,V> getEntry(Object key) {
// Offload comparator-based version for sake of performance
if (comparator != null)
return getEntryUsingComparator(key);
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
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)
p = p.left;
else if (cmp > 0)
p = p.right;
else
return p;
}
return null;
}
TreeMap的put方法
根据元素的自然排序或者比较器排序,先跟当前集合中的节点的键值进行比较,如果小于当前节点的键值,就跟当前节点的左孩子比较,否则跟当前节点的右孩子进行比较。如果查找到相同的键值则将当前节点的值更新为新值,如果没有和要插入的key值相同的键值,就创建一个新的entry,根据比较的结果确定它是左孩子还是右孩子。
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;//根据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;
}
fixAfterInsertion方法源码
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;
}
新插入的节点置为红色,从新插入的节点开始进行调整,
如果当前节点不为空,不为根节点,而且当前节点的父节点为红色,那么说明需要进行调整。
调整过程:
1,如果当前节点的父节点是祖父节点的左孩子,对祖父节点的右孩子y进行判断
1.1如果y为红色,将当前节点的父节点设为黑色,节点y也设为黑色,及将当前节点的父节点和叔父节点都设为黑色。将祖父节点设为红色。
将当前节点的祖父节点设置为新的节点。
1.2若y不为红色,如果当前节点X为右孩子,将当前节点的父节点设置为当前节点,然后开始对新的当前节点x进行左旋。左旋完毕后,将当前节点x的父节点设置为黑色,祖父节点设置为红色。对祖父节点进行右旋。
2,如果当前节点的父节点是祖父节点的右孩子,对祖父节点的左孩子y进行判断。
2.1如果y为红色。
将当前节点的父节点设为黑色,
将叔父节点y设为黑色,
将祖父节点设为红色,
将当前节点x更新为祖父节点。
2.2如果y不为红色
2.2.1(如果x为左孩子
将x更新为父节点。
对x进行右旋。)
将当前节点的父节点设为黑色
将祖父节点设为红色
对祖父节点进行左旋。
1和2完毕之后对新节点x再次进行判断。如需要调整重复1,2的过程。
当调整完毕后将根节点设置为黑色,这是为了确保根节点为黑色。