二叉查找树 BST : https://blog.csdn.net/cj_286/article/details/90183298
二叉平衡树 AVL : https://blog.csdn.net/cj_286/article/details/90217072
红黑树 RBT : https://blog.csdn.net/cj_286/article/details/90245150
红黑树(Red Black Tree) 是一种自平衡二叉查找树
红黑树和AVL树类似,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。
红黑树是每个节点都带有颜色属性的二叉查找树,颜色或红色或黑色。其性质如下:
性质1. 节点是红色或黑色。
性质2. 根节点是黑色。
性质3 每个叶节点(NULL节点,空节点)是黑色的。
性质4 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
性质5. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
红黑树又叫RedBlackTree、RBT,而且性质5也被称为黑高、BlackHeight、BH
RBT也必定是一个BST(二叉搜索树)
任意一棵以黑色节点为根的子树也必定是一棵红黑树(与BST、AVL的递归定义类似)
首先来看一个黑高为3的一棵红黑树
rbt-1-1-1在实际应用中可省略null节点,如图所示
rbt-1-1-2红黑树不一定是一棵平衡的二叉搜索树,如下图是一棵黑高为2的红黑树,但是它不是一棵平衡树
rbt-1-2-1所以得出以下结论:
1.红黑树不像AVL树一样,永远保持绝对的平衡
2.红黑树是一种相对平衡的二叉树
3.红黑树,若H(left) >= H(right),则:H(left) <= 2*H(right) + 1 ,但BH(left) == BH(right)
若H(left) < H(right),则:H(right) < 2*H(left) + 1 ,但BH(left) == BH(right)
4.定理:N个节点的RBT,最大高度是2log(N+1) ,这个是严格证明的
5.查询效率AVL略好于RBT
6.插入删除效率RBT比AVL好一点(总的来说,插入删除查询结合,RBT要好一点)
在前两章中,已经解决了BST的插入,删除,查询(put、remove、get、getEntry、deleteEntry)
AVL的旋转,插入调整,删除调整(rotateRight、rotateLeft、fixAfterInsertion、fixAfterDeletion)
还有获取最小节点,最大节点,前趋节点,后继节点(getFirstEntry、getLastEntry、successor、predecessor)
RBT其实和AVL一样,AVL是在插入和删除时,分别调用fixAfterInsertion、fixAfterDeletion使其保持平衡,RBT也一样,在BST插入删除时,分别调用fixAfterInsertion、fixAfterDeletion使其保持满足红黑树的那五条性质即可
RBT的插入调整分三种情况:case1、case2、case3,删除调整分四种情况:case1、case2、case3、case4,解决了这些红黑树就完成了。下面分别讨论
RBT插入删除调整是自顶向下还是自底向上?
RBT与AVL类似,在调整某个节点p之前,必须先保证p的左子树left、右子树right都已经是RBT,所以这是一个由多个子问题成立来决定总问题是否成立的算法,所以RBT的插入、删除调整算法均是自底向上(bottom up)
调整算法的递归形式的伪代码
function fixRBTPostOrder(Node p){
if(p != null){
fixRBTPostOrder(p.left);
fixRBTPostOrder(p.left);
dealWith(p);
}
}
JDK TreeMap的fixAfterInsertion源码分析
private static boolean colorOf(Entry p) {
return (p == null ? BLACK : p.color);
}
private static Entry parentOf(Entry p) {
return (p == null ? null: p.parent);
}
private static void setColor(Entry p, boolean c) {
if (p != null)
p.color = c;
}
private static Entry leftOf(Entry p) {
return (p == null) ? null: p.left;
}
private static Entry rightOf(Entry p) {
return (p == null) ? null: p.right;
}
这些函数都是做判空处理的一些简单封装
/** From CLR */
private void fixAfterInsertion(Entry x) {
x.color = RED;
while (x != null && x != root && x.parent.color == RED) {
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
Entry 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 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;
}
从x = parentOf(parentOf(x));可以看出,从插入的节点开始,不断回溯的过程,如果是新增,插入的节点肯定是叶子节点,所以从底开始不断的向上回溯调整RBT,如果是修改插入则无需调整
/** From CLR */
private void fixAfterDeletion(Entry x) {
while (x != root && colorOf(x) == BLACK) {
if (x == leftOf(parentOf(x))) {
Entry sib = rightOf(parentOf(x));
if (colorOf(sib) == RED) {
setColor(sib, BLACK);
setColor(parentOf(x), RED);
rotateLeft(parentOf(x));
sib = rightOf(parentOf(x));
}
if (colorOf(leftOf(sib)) == BLACK &&
colorOf(rightOf(sib)) == BLACK) {
setColor(sib, RED);
x = parentOf(x);//从这里可以看出该算法也是自底向上,指针不断回溯的一个过程
} else {
if (colorOf(rightOf(sib)) == BLACK) {
setColor(leftOf(sib), BLACK);
setColor(sib, RED);
rotateRight(sib);
sib = rightOf(parentOf(x));
}
setColor(sib, colorOf(parentOf(x)));
setColor(parentOf(x), BLACK);
setColor(rightOf(sib), BLACK);
rotateLeft(parentOf(x));
x = root;
}
} else { // symmetric
Entry 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);
}
删除调整和插入调整一样,都是自底向上,不断回溯调整
插入原则
若插入的节点为黑色,肯定违反性质5,所以只能插入红色节点,可能违反性质4,需要继续调整。
RBT的插入调整
考虑插入到左子树的情况,规定如下标记:
1.正在处理的节点X,也叫子节点
2.父节点P
3.爷爷节点G
4.叔叔节点Y
5.A3表示黑高为3的红黑树
插入调整算法的正确性证明
每将节点进行染色、旋转操作,我们需要考虑以下两点
1.是否会引起左右子树BH不一致,即是否满足性质5
2.有无继续破坏性质4的可能
RBT插入调整情况如下
1.无需调整的情况为:
1).X为根节点,将X由红染黑,简称rootOver
2).父节点P为黑色,BlackParentOver,简称bpOver
2.仅仅需要考虑父节点P为红色的情形,由于性质4,爷爷节点G必定为黑色,分为以下三种情况:
1).case1:Y为红色,X可左可右;P、Y染黑,G染红,X回溯至G
2).case2:Y为黑色,X为右孩子;左旋P,X指向P,转化为case3
3).case3:Y为黑色,X为左子树;P染黑,G染红,右旋G,结束
3.结论:RBT的插入调整最多旋转2次
无需调整情况分析
1.越界 (x==null)
2.X是根节点 (x==root)
3.父节点P的颜色为黑色,必定满足性质4 (x.parent.color!=RED)
所以while循环调整的条件语句是while(x!=null && x!=root && x.parent.color==RED)
4.不管根节点是否是黑色,将根节点root染黑
详细分析
case1
1.条件:P为G的左孩子,Y为红色,X可左可右
2.处理方式:P、Y染黑,G染红,X回溯至G
JDK TreeMap源码分析
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {//如果X父亲P是G的左孩子
Entry y = rightOf(parentOf(parentOf(x)));//获取叔叔节点Y
if (colorOf(y) == RED) {//叔叔节点Y为红色,X可左可右
setColor(parentOf(x), BLACK);//父节点P染黑
setColor(y, BLACK);//叔叔节点Y染黑
setColor(parentOf(parentOf(x)), RED);//祖父G染红
x = parentOf(parentOf(x));//指针回溯,X回溯至G
}
case1图解如下所示
rbt-1-3-2case1算法正确性证明
经过case1调整之后:
1.显然X、P、Y、G的关系均满足性质4
2.X的BH未改变,所以P是满足性质5的
3.P、Y的BH同时增加了1,那么G也还是满足性质5的
4.G的BH未改变,所以整个RBT是满足性质5的
5.G是红色,可能违反性质2、性质4,所以需要继续调整 (G为根节点或G的父节点为红色)
case1的转化
由于G是一个红色节点,故case1可转化为:case1;case2;case3;rootOver,把根节点由红染黑,结束调整,此时整个红黑树的BH增加1,这是唯一增加整个树BH的情形。
case2
1.条件:P为G的左孩子,Y为黑色,X为右孩子
2.处理方式:左旋P,X指向P,转化为case3
JDK TreeMap源码分析
else { //叔叔节点Y是黑色
if (x == rightOf(parentOf(x))) { //X是P的右孩子
x = parentOf(x); //X指向P
rotateLeft(x);//左旋P
}
case2图解如下所示
rbt-1-3-3case2算法正确性证明
经过case2调整之后:
1.P左右子树的BH均未改变,所以P满足性质5
2.X左右子树的BH均未改变,所以X满足性质5
3.X、Y的BH均未改变,所以G满足性质5
4.G的BH未改变,所以可以推导出整个RBT满足性质5
5.P、X的关系仍然违反性质4,需要继续调整
case2的转化
1.case2只能转化为case3
2.case2不会引起BH增加
case3
1.条件:P为G的左孩子,Y为黑色,X为左孩子
2.处理方式:P染黑,G染红,右旋G,结束
JDK TreeMap源码分析
setColor(parentOf(x), BLACK);//父节点P染黑
setColor(parentOf(parentOf(x)), RED);//祖父节点G染黑
rotateRight(parentOf(parentOf(x)));//右旋G
case3图解如下所示
rbt-1-3-4case3算法正确性证明
经过case3调整之后:
1.G左右子树的BH均未改变,所以G满足性质5
2.P左右子树的BH均未改变,所以P满足性质5
3.P的BH未改变,所以整个RBT满足性质5
4.P的颜色为黑色,必定满足性质2、性质4,算法结束
case3的转化
case3无需转化,也不会引起BH增加
AVL插入与RBT的插入对比
1.插入元素都是BST的插入,,区别在于插入后的调整
2.旋转次数:AVL与RBT均是O(1) (旋转次数都是常数级别)
3.指针回溯次数,最好情况:
1).很早就遇到单旋或双旋的情况,为O(1)
2).很早就遇到case2或case3,为O(1)
4.指针回溯次数,最坏情况:
1).回溯至根节点才发现平衡因子大于1,为logN
2).不断执行case1,知道根节点,但每次向上回溯两层,为logN / 2
5.插入删除效率:RBT好于AVL
6.查询效率:AVL略好于RBT
进一步细化case
1.为了方便举例和调试,进一步细化case
2.根节点结束rootOver,黑色父亲结束bpOver
3.P为G的左孩子,三个leftCase:
1).leftCase1: Y为红,X可左可右;P、Y变黑,G变红,X变G
2).leftCase2: Y为黑,X为右孩子;左旋P,X变P
3).leftCase3: Y为左孩子;G为左孩子;G变红、P变黑、右旋G
4.P为G的右孩子,三个rightCase:(和3正好对称处理)
1).rightCase1: Y为红,X可左可右;P、Y变黑,G变红,X变G
2).rightCase2: Y为黑,X为左孩子;右旋P,X变P
3).rightCase3: Y为黑,X为右孩子;G变红,P变黑,左旋G
插入演示
依次插入[12,1,9,2,0,11,7,19,4,15,18,5,|14,13,10,16,6,3,8,17]
插入12
12是根节点,将其染黑,结束
插入1
父节点为黑色,直接插入,bpOver
插入9
父节点1是红节点,9是1的右孩子,属于leftCase2,所以将1进行左旋,转成leftCase3,再将12进行右旋,9染成黑色,12染成红色
插入2
父节点1和叔叔节点12都为红色,属于leftCase1,将1、12染黑,9染红,指针回溯至9,9是根节点,9染成黑色,rootOver,BH增加了1
插入0
父节点1为黑色,bpOver
插入11
父节点12为黑色,bpOver
插入7
父节点2和叔叔节点0都为红色,属于rightCase1,将2、0染黑,1染红,指针回溯至1,1的父节点9是黑色的,bpOver
插入19
父节点12为黑色,bpOver
插入4
父节点7是祖父节点2的右节点,且叔叔节点(nulll)是黑节点,属于rightCase2,所以将7进行右旋,转成rightCase3,继续调整,将节点2左旋,将节点4染黑,节点2染红,插入完成
插入15
父节点19和叔叔节点11都是红色节点,属于rightCase1,将19和11染黑,祖父节点12染红,指针回溯至12,12的父节点9是黑色,bpOver
插入18
18是右孩子,父节点15是祖父节点的左孩子,叔叔节点(null)是黑色节点,属于leftCase2,将15左旋,转成leftCase3,继续调整,将爷爷节点19右旋,将19染红,18染黑,插入完成
插入5
5是左孩子,父节点7是祖父节点的右节点,父节7和叔叔节点2都是红色,属于rightCase1,将7和2染黑,祖父节点4染红,指针回溯至4,继续调整,4的父节点和叔叔节点12都是红色的,4的父节点1属于祖父节点9的左孩子,属于leftCase1,将1和12都染黑,将(根)节点9染红,发现9是根节点,再将9染黑,rootOver,BH增加1,插入结束。
RBT的删除原则
1.删除红色节点,不会影响BH,也不会违反性质4,,无需调整
2.删除黑色节点,节点所在子树的BH减1,需要调整
RBT的删除调整
考虑删除左子树的情况,规定如下标记:
1.正在处理的节点为X
2.父节点为P
3.兄弟节点为S (sib)
4.左侄子(兄弟节点的左孩子)为LN
5.右侄子(兄弟节点的右孩子)为RN
删除调整算法的正确证明
1.每将节点进行染色、旋转操作,都需要考虑:
1).是否违反性质5,如:
X的BH只能不变或增加,否则X的BH将比S的更小
2).是否违反性质4,如果违反,染黑还是继续回溯
RBT的删除调整 (删除的节点是替换的叶子节点)
1.需要删除的节点X为红色,直接删除X
2.其他无需调整的情况为:
1).当前X为根节点,无论root是什么颜色,都将root染黑,简称rootOver
2).当前X为红色,将X染黑,结束,简称redOver
3.删除左孩子X,分为四种情况:
case1: S为红色;S染黑,P染红,左旋P
case2: S为黑色,黑LN,黑RN;S染红,X回溯至P
case3: S为黑色,红LN,黑RN;LN染黑,S染红,右旋S;转成case4
case4: 黑S,LN随意,红RN;S变P的颜色,P和RN染黑,左旋P
4.删除右孩子X,情况正好和删除左孩子X对称
详细分析
无需调整1
1.删除的X本身就是红色节点,直接删除
JDK TreeMap源码分析
// Fix replacement
if (p.color == BLACK)
fixAfterDeletion(replacement);
} else if (p.parent == null) { // return if we are the only node.
root = null;
} else { // No children. Use self as phantom replacement and unlink.
if (p.color == BLACK)
fixAfterDeletion(p);
只有在删除节点是黑色的请款才需要调整
无需调整2
1.回溯指针时遇到的情形:
1).当前X为根节点,无论root是什么颜色,都将root染黑,将根节点染黑的同时满足性质2、4、5,简称rootOver
2).当前X为红色,将X染黑,结束,简称redOver
(在执行这一步之前,BH(left)==BH(right)-1,执行完这一步,正好使得BH(left)==BH(right),删除调整算法结束)
JDK TreeMap源码分析
while (x != root && colorOf(x) == BLACK) {
if (x == leftOf(parentOf(x))) {
...
}
setColor(x, BLACK);
需要调整的情况
case1
1.条件:S为红色
2.隐含条件:由于性质4,P、LN、RN必定都为黑色
处理方式:S染黑,P染红,旋转P,LN成为新的S(sib)
JDK TreeMap源码分析
if (x == leftOf(parentOf(x))) { //X是P的左孩子
Entry sib = rightOf(parentOf(x));//获取兄弟节点S
if (colorOf(sib) == RED) {//S为红色
setColor(sib, BLACK);//S染黑
setColor(parentOf(x), RED);//P染红
rotateLeft(parentOf(x));//左旋P
sib = rightOf(parentOf(x));//原来的LN成为新的S
}
case1图解如下所示
rbt-1-5-2case1的正确性证明
经过case调整之后:
1.符合性质4
2.BH(X)比BH(LN)少1,则P违反了性质5
3.P违反性质5,则S也违反了性质5
4.需要继续调整X
case1的转化情况
1.case1可转化为:
1).case2-2
2).case3
3).case4-1、case4-2
2.case1不会引起BH的变化
case2
1.case2-1条件:S、LN、RN均为黑色,P为黑色
2.case2-2条件:S、LN、RN均为黑色,P为红色
3.处理方式相同:S染红,X回溯至P
JDK TreeMap源码分析
if (colorOf(leftOf(sib)) == BLACK &&
colorOf(rightOf(sib)) == BLACK) { //S、LN、RN均为黑色
setColor(sib, RED); //将S染红
x = parentOf(x); //X回溯至P
}
case2-1图解如下所示
rbt-1-6-1case2-1的正确性证明
经过case2-1调整之后:
1.符合性质4
2.S的BH减少了1,BH(X)==BH(S),则P符合性质5
3.P的BH减少了1,则整个RBT违反性质5
4.需要继续调整
case2-1的转化情况
1.由于P是黑色,故case2-1可转化为任意case:
1).case1
2).case2-1、case2-2
3).case3
4).case4-1、case4-2
2.若P为根节点,则执行case2-1会引起BH的减小,这是唯一减小红黑树BH的情形
case2-2图解如下所示
rbt-1-6-2case2-2的正确性证明
1.经过case2-2调整之后
1).BH(S)减少了1,BH(X) == BH(S),则P符合性质5
2).P的BH减少了1,则整个RBT违反了性质5
3).P与S的关系违反了性质4
2.调整策略
1).redOver
2).直接将P染黑,则BH(P)加1,所以满足性质4且RBT平衡
case2-2的转化情况
1.case2-2只能转化为redOver,并且结束调整
case3
1.条件:S为黑色,LN为红色,RN为黑色
2.处理方式:LN染黑,S染红,右旋S,S指向LN,转化为case4-1或者case4-2
JDK TreeMap源码分析
if (colorOf(rightOf(sib)) == BLACK) { //S为黑色,LN为红色,RN为黑色
setColor(leftOf(sib), BLACK); //LN染黑
setColor(sib, RED); //S染红
rotateRight(sib); //右旋S
sib = rightOf(parentOf(x)); //S指向LN
}
case3图解如下所示
rbt-1-7-1case3的正确性证明
经过case3调整之后
1.S的左右子树BH相等,则S符合性质5
2.LN的左右子树BH相等,则LN符合性质5
3.X的BH仍然比LN的BH少1,所以违反性质5
4.需要继续调整X
case3的转化情况
1.case3可转化为case4-1、case4-2
2.case3不会引起BH的变化
case4
1.条件:S为黑色,P可红可黑,RN为黑色
1).case4-1: LN为红色
2).case4-2: LN为黑色
2.处理方式相同:S的颜色设置为与P相同,P染黑,RN染黑,左旋P,X指向根节点,rootOver
JDK TreeMap源码分析
//S为黑色,P可红可黑,RN为黑色,LN可红可黑
setColor(sib, colorOf(parentOf(x))); //S的颜色设置为与P相同
setColor(parentOf(x), BLACK); //P染黑
setColor(rightOf(sib), BLACK); //RN染黑
rotateLeft(parentOf(x)); //左旋P
x = root; //X回溯至根节点
case4-1图解如下所示
rbt-1-8-1case4-2图解如下所示
rbt-1-8-2case4的正确性证明
经过case4调整之后:
1.染黑之后的P正好填补了左子树缺少的一个BH
2.RN染黑,正好填补了空缺的黑S,右子树的BH不变
3.BH(P)==BH(RN),则符合性质5
4.以S为根的子树BH和删除前一样,所以整个RBT平衡
5.没有任何违反性质4的节点
6.rootOver
case4的转化情况
1.rootOver,无需转化
转化情况与旋转次数
1.完整的case转化情况
1).case1可以转化为:case2-2、case3、case4
2).case2-1可以转化为:case1、case2-1、case2-2、case3、case4
3).case2-2不可转化
4).case3可以转化为:case4
5).case4不可转化
2.RBT的删除调整最多旋转3次 (如:case1 -> case3 -> case4)
AVL的删除和RBT的删除对比
1.删除节点都是BST的删除,主要区别在于调整
2.旋转次数:AVL与RBT均是常数级别,所以是O(1)
3.指针回溯次数,最好情况:
1).AVL:类似插入,可通过优化提前结束递归,为O(1)
2).RBT:很早就遇到case1、case2-2、case3或case4,为o(1)
4.指针回溯次数,最坏情况
1).AVL:回溯至根节点才发现平衡因子大于1,为logN
2).RBT:不断执行case2-1,知道根节点,为logN;但是,RBT大部分形态下是红黑相间的,一直遇不到红色节点的情况很少见
5.删除效率:RBT略微好于AVL
进一步细化删除算法的case
左孩子
1.leftCase1:S为红色;S染黑,P染红,左旋P
2.leftCase2-1:S为黑色,黑LN,黑RN,黑P;S染红,X回溯至P
3.leftCase2-2:S为黑色,黑LN,黑RN,红P;S染红,X回溯至P
4.leftCase3:S为黑色,红LN,黑RN;LN染黑,S染红,右旋S
5.leftCase4-1:黑S,红LN,红RN;S以父为名(S变P的颜色),P和RN染黑,左旋P
6.leftCase4-2:黑S,黑LN,红RN;S以父为名(S变P的颜色),P和RN染黑,左旋P
右孩子
1.rightCase1:S为红色;S染黑,P染红,右旋P
2.rightCase2-1:S为黑色,黑LN,黑RN,黑P;S染红,X回溯至P
3.rightCase2-2:S为黑色,黑LN,黑RN,红P;S染红,X回溯至P
4.rightCase3:S为黑色,红RN,黑LN;RN染黑,S染红,左旋S
5.rightCase4-1:黑S,红LN,红RN;S以父为名(S变P的颜色),P和LN染黑,右旋P
6.rightCase4-2:黑S,红LN,黑RN;S以父为名(S变P的颜色),P和LN染黑,右旋P
二叉树删除节点找替代节点有3中情况:
1.若删除节点无子节点,直接删除
2.若删除节点只有一个子节点,用子节点替换删除节点
3.若删除节点有两个孩子,用后继节点(或前趋节点)替换删除节点
删除节点被替代后,对于树来说,可以认为删除的是替代节点,3中二叉树的删除情景可以互相转换并最终都是转换为情况1、情况2
删除源码的注意点
1.需要successor,对应BST删除的情况3
// If strictly internal, copy successor's element to p and then make p
// point to successor.
if (p.left != null && p.right != null) {
Entry s = successor(p);
p.key = s.key;
p.value = s.value;
p = s;
} // p has 2 children
2.经过successor之后,情况3转化为情况1或情况2
是否需要successor
1.无需successor,直接删除(调整)节点P本身
2.用successor替代P,再进行删除
rbt-1-9-2
BST删除的情况1和情况2的执行顺序略有不同
情况1.先调整P,再删除P
else { // No children. Use self as phantom replacement and unlink.
//情况1:P是叶子节点,直接删除
if (p.color == BLACK)
fixAfterDeletion(p);//先调整
//再进行删除(设置为null)
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;
}
}
情况2.先删除P,再调整replacement
// Start fixup at replacement node, if it exists.
Entry replacement = (p.left != null ? p.left : p.right);
//情况2:P只有一个孩子,replacement是孩子之一
if (replacement != null) {
// Link replacement to parent
replacement.parent = p.parent;
if (p.parent == null)
root = replacement;
else if (p == p.parent.left)
p.parent.left = replacement;
else
p.parent.right = replacement;
// Null out links so they are OK to use by fixAfterDeletion.
//先删除(设置为null)
p.left = p.right = p.parent = null;
// Fix replacement
if (p.color == BLACK)
fixAfterDeletion(replacement);//再调整
}
BST删除的情况1
先调整,后删除
BST删除的情况2
先删除,后调整
删除演示
根据JDK TreeMap源码分析
依次插入[12,1,9,2,0,11,7,19,4,15,18,5,14,13,10,16,6,3,8,17]后生成的红黑树
依次删除[12,1,9,2,0,11,7,| 19,4,15,18,5,14,13,10,16 |,6,3,8,17]
依次删除[12,1,9,2,0,11,7]之后的红黑树
删除19
删除19是BST的删除情况1,先调整,后删除
fix(p) -> rightCase4-1 -> 18染黑、16染红、15染黑,右旋18 -> 设置p=null
删除4
删除4是BST的删除情况3,先找到后继节点successor=5
赋值之后,指针指向了后继节点5,这时是BST的删除情况1,先调整,后删除;fix(p) -> leftCase2-2
rbt-1-10-48染红,节点x向上回溯至6,6为红色 -> redOver,6染黑 -> 设置p=null ,删除节点5 (图中的x为调整函数过程中的当前节点,p为传入调整函数时的节点,即p使调整之前的节点5)
rbt-1-10-5删除15
删除的15属于叶子节点,所以属于BST删除的删除情况1,先调整,后删除
fix(p) -> leftCase3 ->18染红、17染黑、右旋18
这时就转化成了leftCase4-2 -> 16染黑、17染红、18染黑、左旋16 -> 设置p=null ,删除15
rbt-1-10-7删除18
删除18属于BST的删除情况1,先调整,后删除
fix(p) -> rightCase2-2 -> 16染红、指针回溯至17 -> 17为红色 , redOver -> 17染黑 -> 设置p=null ,删除节点18
删除5
删除5属于BST的删除情况3,先找到后继节点successor=6 ,用6替代5,然后指针P指向后继节点6
这时属于BST的删除情况2 -> 6有一个右孩子,所以将6删掉,右孩子8顶替上来,replacement = 8 -> 设置p=null -> fix(rep) -> 8为红色,redOver,8染黑
rbt-1-10-10删除14
14即右左孩子又有右孩子,数据BST的情况3,先找到后继节点successor=16 ,用16替代14 ,P指针指向16
这时属于BST的删除情况1,并且后继节点16属于红色节点,所以直接删除16 -> 设置p=null (这时的二叉树正好是满二叉树,并且都是黑色节点)
rbt-1-10-12删除13
删除13属于BST的删除情况1,先调整,后删除
fix(p) -> leftCase2-1 -> 17染红、指针回溯至16 -> 6、3、8、10都是黑色,则属于rightCase2-1 -> 6染红,指针回溯至根节点10 -> rootOver , 10染黑(本来就是黑色) ->设置p=nul ,删除13 (删除13执行了两次2-1,且10为根节点,所以删除13会使整个BH减少1)
删除10
10既有左孩子又有右孩子,属于BST的删除情况3,先找到后继节点successor=16,用16替代10,然后指针P指向后继节点16
这时属于BST的删除情况2,16有一个右孩子,所以将16删掉,右孩子17顶替上来,replacement = 17 -> 设置p=null -> fix(rep) -> 17为红色,redOver,17染黑 (删除函数中待调整的节点为rep,删除调整函数中当前调整节点为x)
rbt-1-10-15删除16
16既有左孩子又有右孩子,属于BST的删除情况3,先找到后继节点successor=17,用17替代16,然后指针P指向后继节点17
fix(p) -> 17的兄弟节点为红色,属于rightCase1 -> 6染黑、根节点17染红、右旋根节点17
rbt-1-10-17x指向了17,兄弟节点8为黑色,8的两个孩子也是黑色(NULL),父节点17为红色,属于rightCase2-2 -> 8染红,指针回溯至17 -> 17为红色,redOver,17(x指针,x在删除调整函数fixAfterDeletion中)染黑 -> 设置p=nul ,删除17(p指针,p在删除函数deleteEntry中)
rbt-1-10-18
import java.util.*;
public class RBTMap implements Iterable>{
private int size = 0;
private RBNode root;//根节点
private Comparator super K> comparator;
public RBTMap(Comparator comparator) {
this.comparator = comparator;
}
public RBTMap() {
}
public int size() {
return this.size;
}
public boolean isEmpty() {
return this.size == 0;
}
public int compare(Object a, Object b) {
if (comparator != null) {
return comparator.compare((K)a,(K)b);//JDK中也是强转的
}else{
Comparable t = (Comparable) a;
return t.compareTo((K)b);
}
}
private RBNode parentOf(RBNode p) {
return p!=null ? p.parent : null;
}
private RBNode leftOf(RBNode p){
return p != null ? p.left : null;
}
private RBNode rightOf(RBNode p){
return p != null ? p.right : null;
}
private boolean isRed(RBNode p) {
return ((p!=null)&&(p.color==RED)) ? true : false;
}
private boolean isBlack(RBNode p) {
return !isRed(p);
}
private boolean colorOf(RBNode p) {
return (p == null ? BLACK : p.color);
}
private void setBlack(RBNode p) {
if (p!=null)
p.color = BLACK;
}
private void setRed(RBNode p) {
if (p!=null)
p.color = RED;
}
private void setColor(RBNode p,boolean color){
if (p != null) {
p.color = color;
}
}
/*************对红黑树节点p进行左旋操作 ******************/
/*
* 左旋示意图:对节点p进行左旋
*
* p.p p.p
* / /
* p r
* / \ / \
* l r -----> p r.r
* / \ / \
* r.l r.r l r.l
*
* 左旋做了三件事:
* 1. 将r的左子节点赋给p的右子节点,并将p赋给r左子节点的父节点(r左子节点非空时)
* 2. 将p的父节点p.p(非空时)赋给r的父节点,同时更新p.p的子节点为r(左或右)
* 3. 将r的左子节点设为p,将p的父节点设为r
*/
private void rotateLeft(RBNode p) {
if (p == null) return;
//1. 将r的左子节点赋给p的右子节点,并将p赋给r左子节点的父节点(r左子节点非空时)
RBNode r = p.right;
p.right = r.left;
if (r.left != null) {
r.left.parent = p;
}
//2. 将p的父节点p.p(非空时)赋给r的父节点,同时更新p.p的子节点为r(左或右)
r.parent = p.parent;
if (p.parent == null) {
this.root = r;//如果p的父节点为空(即p为根节点),则将r设为根节点
}else {
if (p == p.parent.left) {//如果p是左子节点
p.parent.left = r;//则也将r设为左子节点
}else{
p.parent.right = r;//否则将r设为右子节点
}
}
// 3. 将r的左子节点设为p,将p的父节点设为r
r.left = p;
p.parent = r;
}
/*************对红黑树节点p进行右旋操作 ******************/
/*
* 右旋示意图:对节点p进行右旋
*
* p.p p.p
* / /
* p l
* / \ / \
* l r -----> l.l p
* / \ / \
* l.l l.r l.r r
*
* 右旋做了三件事:
* 1. 将l的右子节点赋给p的左子节点,并将p赋给l右子节点的父节点(l右子节点非空时)
* 2. 将p的父节点p.p(非空时)赋给l的父节点,同时更新p.p的子节点为l(左或右)
* 3. 将l的右子节点设为p,将p的父节点设为l
*/
private void rotateRight(RBNode p) {
if(p == null) return;
//1. 将l的右子节点赋给p的左子节点,并将p赋给l右子节点的父节点(l右子节点非空时)
RBNode l = p.left;
p.left = l.right;
if (l.right != null) {
l.right.parent = p;
}
//2. 将p的父节点p.p(非空时)赋给l的父节点,同时更新p.p的子节点为l(左或右)
l.parent = p.parent;
if (p.parent == null) {
this.root = l;//如果p的父节点为空(即p为根节点),则旋转后将l设为根节点
}else{
if (p == p.parent.right) {//如果p是右子节点
p.parent.right = l;//否则将l设置为右子节点
}else{
p.parent.left = l;//则将l也设置为左子节点
}
}
//3. 将l的右子节点设为p,将p的父节点设为l
l.right = p;
p.parent = l;
}
public V put(K key,V value) {
RBNode e = null;
if (root == null) {
root = new RBNode(key,value,BLACK);
e = root;
size ++;
}else{
RBNode p = root;
while (p != null) {
int cmp = compare(key,p.key);
if (cmp < 0) {
if (p.left == null) {
p.left = new RBNode(key,value,BLACK,p);
e = p.left;
size ++;
break;
}else{
p = p.left;//再次循环比较
}
} else if (cmp > 0) {
if (p.right == null) {
p.right = new RBNode(key,value,BLACK,p);
e = p.right;
size ++;
break;
}else{
p = p.right;
}
}else{
p.setValue(value);//替换旧值
e = p;
break;
}
}
}
fixAfterInsertion(e);
//不管是插入的是新值还是重复值,都返回插入的值,这个和JDK TreeMap不一样
return value;
}
/**
* 插入调整
* 自底向上
* @param x
*/
private void fixAfterInsertion(RBNode x) {
setRed(x);
RBNode parent;//定义父节点
//需要修正的条件:父节点存在,且父节点的颜色是红色
while ((parent = parentOf(x))!=null && isRed(parent)){
//若父节点是祖父节点的左子节点,下面是else相反
if (parent == leftOf(parentOf(parent))) {
RBNode uncle = rightOf(parentOf(parent));//获得叔叔节点
//case1:叔叔节点也是红色
if (uncle != null && isRed(uncle)) {
setBlack(parent);//把父节点和叔叔节点涂黑
setBlack(uncle);
setRed(parentOf(parent));//把祖父节点涂红
x = parentOf(parent);//把位置放到祖父节点处
continue;
}
//case2:叔叔节点是黑色,且当前节点是右子节点
if (parent.right == x) {
x = parent;
rotateLeft(x);
}
//case3:叔叔节点是黑色,且当前节点是左子节点
setBlack(parent);
setRed(parentOf(parent));
rotateRight(parentOf(parent));
}else {//若父节点是祖父节点的右子节点,与上面的情况完全相反,本质是一样的
RBNode uncle = leftOf(parentOf(parent));
//case1:叔叔节点也是红色的
if (uncle != null && isRed(uncle)) {
setBlack(parent);
setBlack(uncle);
setRed(parentOf(parent));
x = parentOf(parent);
continue;
}
//case2:叔叔节点是黑色的,且当前节点是左子节点
if (x == parent.left) {
x = parent;
rotateRight(x);
}
//case3:叔叔节点是黑色的,且当前节点是右子节点
setBlack(parent);
setRed(parentOf(parent));
rotateLeft(parentOf(parent));
}
}
setBlack(root);
}
private RBNode getEntry(Object key) {
RBNode p = root;//初始化指针p,指向根节点
while (p != null) {
int cmp = compare(key, p.key);//比较key与p.key的大小
if (cmp < 0) {
p = p.left;//key小于p.key,递归(循环)查找左子树
} else if (cmp > 0) {
p = p.right;//key大于p.key,递归(循环)查找右子树
} else {
return p;//key等于p.key,查找成功
}
}
return null;//查找失败
}
public V get(Object key) {
RBNode entry = getEntry(key);
return entry != null ? entry.value : null;
}
/**
* 找到最小结点(中序遍历的第一个结点)
* @return
*/
public RBNode getFirstEntry() {
return getFirstEntry(root);
}
/**
* 找到以p为根节点的最小结点(中序遍历的第一个结点)
* @param p
* @return
*/
private RBNode getFirstEntry(RBNode p) {
if(p == null)
return null;
while (p.left != null)//一直往左找,直到p没有左子树
p = p.left;
return p;//p是最左边且第一个没有左子树的结点
}
/**
* 找到最大结点(中序遍历的最后一个结点)
* @return
*/
public RBNode getLastEntry() {
return getLastEntry(root);
}
/**
* 找到以p为根节点的最大结点
* @param p
* @return
*/
private RBNode getLastEntry(RBNode p) {
if (p == null)
return null;
while (p.right != null)//一直往右找,直到p没有右子树
p = p.right;
return p;//p是最右边且第一个没有右子树的结点
}
/**
* 删除
* @param key
* @return
*/
public V remove(K key) {
RBNode entry = getEntry(key);//查找是否有该结点
if (entry == null) {
return null;
}
V oldValue = entry.getValue();//获取value
deleteEntry(entry);
return oldValue;
}
/**
* 删除结点p
*
* 二叉树删除节点找替代节点有3中情况
* 情况1.若删除节点无子节点,直接删除
* 情况2.若删除节点只有一个子节点,用子节点替换删除节点
* 情况3.若删除节点有两个孩子,用后继节点(或前趋节点)替换删除节点
* 删除节点被替代后,对于树来说,可以认为删除的是替代节点,3中二叉树的删除情景可以互相转换并最终都是转换为情况1、情况2
*
* BST删除的情况1和情况2的执行顺序略有不同
* 情况1.先调整P,再删除P
* 情况2.先删除P,再调整rep
*
* RBT的删除调整
* 1.需要删除的节点X为红色,直接删除X
* 2.其他无需调整的情况为:
* 1).当前X为根节点,无论root是什么颜色,都将root染黑,简称rootOver
* 2).当前X为红色,将X染黑,结束,简称redOver
* 3.删除左孩子X,分为四种情况:
* case1: S为红色;S染黑,P染红,左旋P
* case2: S为黑色,黑LN,黑RN;S染红,X回溯至P
* case3: S为黑色,红LN,黑RN;LN染黑,S染红,右旋S;转成case4
* case4: 黑S,LN随意,红RN;S变P的颜色,P和RN染黑,左旋P
* 4.删除右孩子X,情况正好和删除左孩子X对称
* @param p 要删除的结点
*/
private void deleteEntry(RBNode p){
size --;
//情况3 删除节点被替代后,对于树来说,可以认为删除的是替代节点,3中二叉树的删除情景可以互相转换并最终都是转换为情况1、情况2,所以先执行情况3
if (p.left != null && p.right != null) { //情况3
////为了达到平衡效果,随机执行以下两种情况
if ((size & 1) == 0) {//找右子树中最小的来替换
RBNode rightMin = getFirstEntry(p.right);//也可以使用获取p的后继节点方法(successor(p))
p.key = rightMin.key;
p.value = rightMin.value;
p = rightMin;
}else {//找左子树中最大的来替代
RBNode leftMax = getLastEntry(p.left);//也可以使用获取p的前趋节点方法(predecessor(p))
p.key = leftMax.key;
p.value = leftMax.value;
p = leftMax;
}
}
//以下顺序不能变(重要)
//1.p结点只有一个孩子
// 如果整棵树只有两个结点的时候,这是删除的是根结点,如果先执行2和3的话就会直接将root置为null
//2.p为根结点
// 如果整棵树只有一个结点的时候,这是删除的是跟结点,如果先执行3的话,因为这时根结点也是叶子结点,就根本不会执行2了,也就不会将root置为null了,导致根结点永远删除不了
//3.p为叶子结点
if (p.left == null && p.right != null) {//情况2
RBNode rep = p.right;
deleteOneEntry(p,rep);
} else if (p.left != null && p.right == null) {//情况2
RBNode rep = p.left;
deleteOneEntry(p,rep);
} else if (p.parent == null) { //如果是根结点直接将root置null
root = null;
} else if (p.left == null && p.right == null) {//情况1
//情况1.先调整P,再删除P
if (isBlack(p)) {//删除的是黑色结点才需要调整
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;//断开与父节点的连接
}
}
}
/**
* 情况二删除
* 若删除节点只有一个子节点,用子节点替换删除节点
* @param p
* @param rep
*/
private void deleteOneEntry(RBNode p,RBNode rep){
//情况2.先删除P,再调整rep
rep.parent = p.parent;
if (p.parent == null) { //删除的p是根结点,直接将p.right设置成root
root = rep;
} else if (p == p.parent.left) { //p为左孩子
p.parent.left = rep;
} else { //p为右孩子
p.parent.right = rep;
}
//释放p
p.left = p.right = p.parent = null;
//调整
if (isBlack(p)) {//删除的是黑色结点才需要调整
fixAfterDeletion(rep);
}
}
/**
* 删除调整
* @param x
*/
private void fixAfterDeletion(RBNode x){
while (x != root && isBlack(x)){
if (x == leftOf(parentOf(x))) { //删除左孩子X ,leftCase
RBNode sib = rightOf(parentOf(x));
//case1: S为红色;S染黑,P染红,左旋P,LN成为新的S(sib)
if (isRed(sib)) {
setBlack(sib);
setRed(parentOf(x));
rotateLeft(parentOf(x));
sib = rightOf(parentOf(x));//叔叔结点sib是相对于x结点来说的
}
//case2: S为黑色,黑LN,黑RN;S染红,X回溯至P
if (isBlack(leftOf(sib)) && isBlack(rightOf(sib))) { //这里S肯定为黑色
setRed(sib);
x = parentOf(x);
}
//case3: S为黑色,红LN,黑RN;LN染黑,S染红,右旋S,S指向LN ;转成case4
else { //这里S肯定为黑色
if (isBlack(rightOf(sib))) { //这里LN肯定为红色
setBlack(leftOf(sib));
setRed(sib);
rotateRight(sib);
//叔叔结点sib是相对于x结点来说的
sib = rightOf(parentOf(x));//旋转后,LN转到了S的位置,所以S需要执行LN的位置
}
//case4: 黑S,LN随意,红RN;S变P的颜色,P和RN染黑,左旋P,X指向根节点,rootOver
//这里的S肯定为黑色,RN肯定为红色(case3右旋S后,sib变为新的RN,case3已经将sib染成了红色)
setColor(sib,colorOf(parentOf(x)));
setBlack(parentOf(x));
setBlack(rightOf(sib));
rotateLeft(parentOf(x));
x = root;
}
}else{ ////删除右孩子X ,rightCase
RBNode sib = leftOf(parentOf(x));
//case1:S为红色;S染黑,P染红,右旋P,RN成为新的S(sib)
if (isRed(sib)) {
setBlack(sib);
setRed(parentOf(x));
rotateRight(parentOf(x));
sib = leftOf(parentOf(x));
}
//case2:S为黑色,黑LN,黑RN;S染红,X回溯至P
if (isBlack(rightOf(sib)) && isBlack(leftOf(sib))) { //S肯定为黑色
setRed(sib);
x = parentOf(x);
}
//case3:S为黑色,红RN,黑LN;RN染黑,S染红,左旋S,S指向RN ;转成case4
else{
if (isBlack(leftOf(sib))) {//如果LN为黑色,RN肯定为红色
setBlack(rightOf(sib));
setRed(sib);
rotateLeft(sib);
sib = leftOf(parentOf(x));
}
//case4-1:黑S,红LN,RN随意;S以父为名(S变P的颜色),P和LN染黑,右旋P,X指向根节点,rootOver
//这里的S肯定为黑色,LN肯定为红色(case3左旋S后,sib变为新的LN,case3已经将sib染成了红色)
setColor(sib, colorOf(parentOf(x)));
setBlack(parentOf(x));
setBlack(leftOf(sib));
rotateRight(parentOf(x));
x = root;
}
}
}
setBlack(x);
}
//*********************************************************
private static final boolean RED = false;
private static final boolean BLACK = true;
public static class RBNode{
boolean color;//颜色
K key;//关键字
V value;//value去掉就是TreeSet
RBNode left; //左孩子
RBNode right;//右孩子
RBNode parent;//父节点
public RBNode(K key,V value, boolean color, RBNode parent, RBNode left, RBNode right){
this.key = key;
this.value = value;
this.color = color;
this.parent = parent;
this.left = left;
this.right = right;
}
public RBNode(K key, V value,boolean color) {
this.color = color;
this.key = key;
this.value = value;
}
public RBNode(K key, V value,boolean color, RBNode parent) {
this.color = color;
this.key = key;
this.value = value;
this.parent = parent;
}
public boolean isColor() {
return color;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
@Override
public String toString() {
return "RBNode{" +
"color=" + color +
", key=" + key +
", value=" + value +
'}';
}
}
//**********************************************************
@Override
public Iterator> iterator() {
return new RBTIterator<>(root);
}
/**
* 中序遍历实现迭代器
* 这里实现引入Stack,而非递归实现
* @param
* @param
*/
public static class RBTIterator implements Iterator> {
private Stack> stack;
public RBTIterator(RBNode root) {
stack = new Stack<>();
addLeftPath(root);
}
private void addLeftPath(RBNode p) {
while (p != null) {
stack.push(p);
p = p.left;
}
}
@Override
public boolean hasNext() {
return !stack.empty();
}
@Override
public RBNode next() {
RBNode entry = stack.pop();
addLeftPath(entry.right);
return entry;
}
}
//**********************************************************
/**
* 层序遍历
*/
public void levelOrder() {
if(root == null) return;
Queue> queue = new LinkedList<>();
queue.offer(root);
int preCount = 1;
int pCount = 0;
while (!queue.isEmpty()) {
preCount --;
RBNode p = queue.poll();
System.out.print(p + " ");
if (p.left != null) {
queue.offer(p.left);
pCount ++;
}
if (p.right != null) {
queue.offer(p.right);
pCount ++;
}
if (preCount == 0) {
preCount = pCount;
pCount = 0;
System.out.println();
}
}
}
}
源码:
https://github.com/xiaojinwei/java-learning/blob/master/src/com/cj/learn/tree/rbt/RBTMap.java