目录
1.红黑树是啥?
初次接触到红黑树是在hashmap的源码分析,hashmap是一种查找以及删除效率较高的数据结构,但是在单个桶位链表过长(冲突发生了很多次)的情况下hashmap的效率会降低,极端一点,整个hashmap只有一个链表存在的情况下(假设不存在树化机制),它就变成了一个链表。
为了在多数据的情况下提高效率,hashmap选择在单链表长度达到8时将其转化为红黑树。
树结构在插入,删除,查找都有不错的效率,但是一般的二叉树也存在问题,如果左子树和右子树差距过大,那么树的性能也会降低。所以,为了保证树的性能,要让他的子树差距小,进而我们需要按照某些规则向树插入节点。红黑树在二叉树的基础上依靠以下规则维持左子树和右子树的平衡。
- 树中的节点要么是红色,要么是黑色。
- 根节点是黑色,叶子节点是黑色。(但是这里指的叶子节点是空节点)
- 如果一个节点是红色,那么它的子节点是黑色。
- 任意节点到其叶子节点的所有路径都包含相同数目的黑色节点。
(插入的节点都是红色,这是为了防止打乱第四条规则)
2.为什么红黑树能有高效率?
对于树而言,查找,插入,删除的成本正比于查找路径。不难判断,上述红黑树的规则是用来限定查找路径长度的。
那么它如何限制?
如果我们要访问某一个叶子节点。考虑两种极端情况。
情况一:整个访问路径上没有任何的红色节点,那么长度就是黑色节点的长度。(可以看到规则四,因为到每一个叶子节点的黑色节点数目相同,那么黑色节点的长度是不可避免的,所有只有黑色节点的路径是最短路径)
情况二:路径上有尽可能多的红色节点与黑色节点,因为规则三的限制,所以红色节点只能插在黑色节点中间,他们不能连续。此时路径就是黑色节点长度的两倍。
综上所述,最长路径不能超越最短路径的两倍。这也保证红黑树的左右子树差距一定不会过大,查找的稳定性强。
(PS:笔者在查阅资料时看到一种比较硬核的证明方式:https://www.cnblogs.com/skywang12345/p/3245399.html#!comments)
3.红黑树调整原理说明
节点的调整有两种方式,一种是左旋,一种是右旋,虽然只有两种,但是他们也成功的涵盖了所有可能接触到的节点变化(这个变化有的前提条件便是变化符合二叉树的规则)。(这个其实可以用数学归纳法推理,以后有时间补上。)
右旋: 将节点变为左子节点的右节点。
假设对B进行右旋。
左旋:将节点右子节点的左节点。
同样,对B进行左旋
假设三个节点,cur为需要插入的节点,parent节点为父节点,gpa节点为祖父节点,uncle节点叔叔节点
1.情况一:uncle节点为空,parent节点红色,cur节点为父节点的左子节点
此时的处理步骤为:1.将gpa节点右旋(gpa作为parent节点的右孩子存在)
2.修改节点颜色
2.情况2:cur节点为parent 节点的右子节点,uncle为空
1.将parent节点左旋,(相对于情况一,仅仅只是多了这一步,之后的步骤都一样)
2.之后的步骤和情况一一样
3.情况3,4:叔叔节点存在,而且为红色,(分为不同情况仅仅是因为与gpa的关系可能不同)
直接变色,完了之后在gpa节点继续进行调整
4.情况5,6:叔叔节点为黑色(这个情况的处理模式笔者一直觉得有多种情况,反正还是仁者见仁,智者见智)
将gpa节点右旋,然后将gpa修改为红色,parent变为黑色(感觉与直接将gpa变为红色,然后parent变为黑色没有任何区别,仅仅只是不需要去循环调整)
大概的情况也就以上几种了。
4.java代码实现
调整方法代码:存储数据结构类似于map(key,value),所有的排序根据key进行,(key类作为泛型存在,他继承了Comparable方法)
private void adjustNode2(Node node){
Node curNode=node;
Node parent=node.parent;
Node gpa;
while (parent!=null && parent.isRed){
//祖父节点是否存在
if ((gpa=parent.parent)==null){
//不存在即证明parent节点为根节点
break;
}
//父节点的位置
if (gpa.leftCh==parent){
//父节点是祖父节点的左子节点
Node uncle;
if ((uncle=gpa.rightCh)==null){
//没有叔叔节点
//判断子节点位置
if(curNode==parent.leftCh){
//如果是左孩子
//那么gpa右旋(作为左子节点的右孩子(这里就是parent的右孩子))
gpa.isRed=true;
parent.isRed=false;
RightRotation(gpa);
//调整完毕时调整的三层树结构的顶端(此时为curNode)
//为黑色,且叶子节点为红色,那么不会影响树结构
//所以直接跳出
break;
}else {
//右孩子
gpa.isRed=true;
curNode.isRed=false;
LeftRotation(parent);
RightRotation(gpa);
break;//同上
}
}else{
//uncle存在
if (uncle.isRed){
//这种情况下,不论curNode的位置
//直接将gpa变为红色
//parent和uncle变为黑色
//然后重新调整gpa节点就好
uncle.isRed=false;
parent.isRed=false;
gpa.isRed=true;
curNode=gpa;
}else{
//叔叔节点为黑色
parent.isRed=false;
gpa.isRed=true;
RightRotation(gpa);
break;
}
}
}else{
//父节点是祖父节点的右子节点
//同上
Node uncle;
if ((uncle=gpa.leftCh)==null){
if (curNode==parent.rightCh){
parent.isRed=false;
gpa.isRed=true;
LeftRotation(gpa);
break;
}else{
curNode.isRed=false;
gpa.isRed=true;
RightRotation(parent);
LeftRotation(gpa);
break;
}
}else{
if (uncle.isRed){
gpa.isRed=true;
parent.isRed=false;
uncle.isRed=false;
curNode=uncle;
}else{
parent.isRed=false;
gpa.isRed=true;
LeftRotation(gpa);
break;
}
}
}
}
//左旋右旋的过程中会去修改根节点
root.isRed=false;
}
以上,如果有什么错误,欢迎指出.