说起红黑树,刚开始看的时候真的很头大,但是这个东西不看也不行,实际应用挺广的,jdk1.8的HashMap和1.2版本后的TreeaMap实现都用到了红黑树,先来说一下红黑树的五个特性吧,只有牢牢熟记这五个特性,才能理解后续的插入和删除操作.
红黑树特性:
1.每个节点不是红色就是黑色的;
2.根节点一定是黑色的;
3.如果一个节点是红色的,则它的孩子(左右都包括,只要有)一定是黑色的;
4NIL节点是黑色的
5红黑树是黑色节点的avl树,即从根节点到任意一个叶子节点所经过的黑色节点数相同(比起AVL严格的平衡来说,红黑树的平衡实现起来效率要高)
记住了这五个特性,下面就来根据TreeMap(jdk1.7)的源码来分析一下插入操做
首先初始化一个TreeMap,不带有比较器的,用的TreeMap的默认比较器
TreeMap<Integer, String> tm=new TreeMap<>();
public TreeMap() {
comparator = null;
}//默认构造函数
然后添加一个数据
tm.put(100, "100");执行put操做,接下来看一下put的源码
Entry<K,V> t = root;//初始root为null
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
所以TreeMap放置第一个对象的时候只是new了一个Entry对象,在newEntry对象过程中,把root节点颜色设置为黑色
接下来添加第二个对象
tm.put(80, "80");由于之前已经有root了,而且初始化TreeMap的时候没有添加一个比较器,所以这次添加对象的流程有一些变化
if (key == null)
throw new NullPointerException();
Comparable<? super K> k = (Comparable<? super K>) key; //此时key为80
do {
parent = t; //t为100
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);//只要t有左孩子或者右孩子就一直循环,直到找到叶子节点(没有孩子的节点)
Entry<K,V> e = new Entry<>(key, value, parent);//构建一个parent为100的节点
if (cmp < 0)
parent.left = e;//把80节点构建为100节点的左孩子
else
parent.right = e;
fixAfterInsertion(e);//这是涉及红黑树真正实现的地方,上述流程只是把节点插入到了红黑树,但是有可能会破坏红黑树的性质,所以需要根据不同的场景进行红黑树的调整
接下来就分析一下fixAfterInsertion(Entry<K,V> x)方法
private void fixAfterInsertion(Entry<K,V> x) {
x.color = RED;//把插入的节点先染成红色
while (x != null && x != root && x.parent.color == RED) {//如果当前结果不为空且当前结果不是树根且当前元素的父亲为红色的,则不满足性质3(两个红色节点不能连续存在)这里多说两句,如果插入结果的父亲是黑色的,则直接插入一个红色孩子不会破坏红黑树的性质
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);//把祖父节点染成红色,因为祖父节点的两个孩子都染成了黑色,导致性质5(根节点至叶子节点所经过的黑色节点数目相同)导致经过这两条路线的黑色节点增加了1,所以祖父节点染成红色,满足了性质5
x = parentOf(parentOf(x));//把祖父节点置为当前节点,继续算法运行
} else {//进入这个条件里面,说明叔叔节点是黑色的
if (x == rightOf(parentOf(x))) {//如果当前节点是父节点的右孩子
x = parentOf(x);//把父节点置为当前节点
rotateLeft(x);//左旋
}
setColor(parentOf(x), BLACK);//把父节点(注意这时候x节点已经变了,所以父节点也变了,这里的父节点指的是旋转后形成新树的父节点)设置为黑色黑色
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);//如果当前节点是父节点的右孩子,则置父节点为红色(注意,如果上面的if条件进入则当前节点已经被重新复制,这点需要注意)
setColor(parentOf(parentOf(x)), RED);//祖父节点置为红色
rotateLeft(parentOf(parentOf(x)));//然后以祖父节点为当前节点左旋
}
}
}
root.color = BLACK;//根元素一定要保持黑色
}
至此TreeMap的添加操作,设计红黑树的插入操作就算完成了.下面来进行一下总结:
1.如果要插入的节点的父节点是黑色的,则直接插入红色节点,不会破坏红黑树性质
2.如果要插入节点的父节点是黑色的,且插入节点的叔叔节点是红色的,则可以把父节点和叔叔节点都染成黑色的,然后把祖父节点染成红色的,并置祖父节点为新的当前节点
3.如果要插入的节点的父节点是红色的并且是祖父节点的左孩子,且叔叔节点是黑色的(或者没有叔叔节点),且插入节点是父亲节点的左孩子,则把祖父节点染成红色,父亲节点染成黑色,以祖父节点为当前节点进行右旋
4.如果要插入的节点的父节点是红色的并且是祖父节点的左孩子,且叔叔节点是黑色的(或者没有叔叔节点),且插入的节点是父节点的右孩子,则把父节点置为当前节点,进行左旋,然后进行步骤3.
5.如果要插入的节点的父节点是红色的并且是祖父节点的右孩子,且叔叔节点是黑色的(或者没有叔叔节点),且插入节点是父节点的右孩子,则把父节点染成黑色,祖父节点染成红色,以祖父节点为新的当前节点进行左旋;
6.如果要插入节点的父节点是红色的并且是祖父节点的右孩子,且叔叔节点是黑色的(或者没有叔叔节点),且插入节点是父节点的左孩子,则以父节点为新的当前节点进行右旋,然后进行步骤5.
本文详细解析红黑树的基本特性及其在Java TreeMap中的应用,重点介绍红黑树的插入操作,通过具体实例帮助理解红黑树如何维持其特性。
8880

被折叠的 条评论
为什么被折叠?



