本文目录
一、为什么要创建红黑树这种数据结构
在上篇我们了解了AVL树,既然已经有了AVL这种平衡的二叉排序树,为什么还要有红黑树呢?
AVL树通过定义我们知道要求树中每一个结点的左右子树高度差的绝对值不超过1,其是一颗严格的平衡树,这样构建出来的平衡二叉排序树具有很好的查找性能,但是为了保持其每个结点平衡因子绝对值不超过1的特性在插入或者删除的时候需要的维护成本是很大的,插入或者删除需要大量的平衡度计算,比如上一篇在AVL的插入的时候就需要不断回溯其父节点调整平衡因子的值,数据量小没什么问题,但是实际应用中往往数据量是很大的,导致AVL树的插入删除维护成本会很高。
此时,就有一部分前人们思考可不可以发明一种数据结构查找性能和AVL树差不多,但是插入删除的时候不需要那么大的维护开销呢?
1972年,鲁道夫·贝尔老爷子就发明了这样一种数据结构: 平衡二叉B树,后来在1978年的时候经过修改才叫红黑树。
以上了解了AVL树的问题以及红黑树的发明创造,下面正式讲解红黑树。
二、红黑树定义
直接给出:
1.节点是红色或黑色。
2.根节点是黑色。
3.每个叶子节点都是黑色的空节点(NIL节点)。
4.每个红色节点的两个子节点都是黑色(从每个叶子到根的所有路径上不能有两个连续的红色节点)。
5.从任一节点到其每个叶子结点的所有路径都包含相同数目的黑色节点。
这里的叶子结点指的是NIL结点,而不是我们平时所指的叶子结点。
满足以上条件的二叉排序树就是一颗红黑树,AVL树与红黑树前提都是二叉排序树。
以下就是一颗红黑树:
这里标记出了NIL结点,之后就不在标记出了,忽略不计了。
以上定义就保证了红黑树不需要像AVL树一样保证左右子树高度差绝对值不超过1,但是也保证了红黑树左右子树高度差不会相差2倍以上,怎么理解呢?怎么就保证不相差两倍以上呢?
三、红黑树如何保证左右子树高度差不会相差很大的
先跟着我的思路进行下面操作
比如以上面举例的红黑树为例,我们在根结点的右子树再插入结点,那么这个结点的颜色只能是红色,因为如果插入的是黑色,那么违反红黑树中第5条约束,如图:
如图,如果在根结点右子树再插入黑色结点那么左侧8到7有两个给色结点,而右侧8到11则有3个黑色结点,显然违反第5条约束。
所以我们只能插入红色结点:
在上图基础上我们在继续想右子树插入结点,根据约束4:红色结点的孩子只能是黑结点,然而插入黑色结点又违反约束5,这里就形成了死循环,无法继续下去。
所以极端情况下,对于上述一棵树,在不进行平衡操作的前提下,根结点左右子树高度差最大为2,。
这里只是举了一个例子说明红黑树红黑树是如何保证左右子树高度差不相差很大的,上面说的都是在不进行平衡操作的前提下,真正的红黑树结点是随便插入的,在违反上述约束会进行相应调整。
四、红黑树添加结点
讲解红黑树的添加算法之前我们先看看TreeMap添加数据的逻辑。
TreeMap与HashMap比较最大特点就是其是有序的Map集合,并且其底层数据结构为红黑树,我们先来简单了解下TreeMap的存储结构。
TreeMap每个存储结构为TreeMapEntry类,源码如下:
static final class TreeMapEntry<K,V> implements Map.Entry<K,V> {
K key;
V value;
TreeMapEntry<K,V> left;
TreeMapEntry<K,V> right;
TreeMapEntry<K,V> parent;
boolean color = BLACK;
/**
* Make a new cell with given key, value, and parent, and with
* {@code null} child links, and BLACK color.
*/
TreeMapEntry(K key, V value, TreeMapEntry<K,V> parent) {
this.key = key;
this.value = value;
this.parent = parent;
}
/**
* Returns the key.
*
* @return the key
*/
public K getKey() {
return key;
}
/**
* Returns the value associated with the key.
*
* @return the value associated with the key
*/
public V getValue() {
return value;
}
/**
* Replaces the value currently associated with the key with the given
* value.
*
* @return the value associated with the key before this method was
* called
*/
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
public boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
return valEquals(key,e.getKey()) && valEquals(value,e.getValue());
}
public int hashCode() {
int keyHash = (key==null ? 0 : key.hashCode());
int valueHash = (