敞开成长之旅!这是我参加「日新计划 · 12 月更文应战」的第9天,点击检查活动概况
红黑树删去的代码完成
写在前面
文章摘要
- 删去的节点在右边的状况
- 几种简略的状况
- 兄弟节点是赤色
- 兄弟节点是黑色
- 兄弟节点至少有一个赤色的子节点
- 兄弟节点没有赤色的子节点
- 删去的节点在左面的状况(与上面对称)
- 优化参数&完好代码
阅览准备
- 主张阅览时刻:10 ~ 15 分钟
- 阅览条件:
- 红黑树删去的剖析 -> 红黑树删去的评论剖析♨️♨️♨️
- 本篇文章着重点是红黑树删去的代码完成,能够合作一同看👆👆👆
二、怎么用代码完成红黑树的删去
(0)完成前的准备
-
剖析了这么这么这么久,真是长叹气以掩涕兮了😨🥶😭
-
只不过看看《红黑树的删去》中对删去的总结,其实也并不是学不会,对吧,假如还有些迷糊,再来看看代码是怎么完成的,更迷糊一些吧~
-
在开端完成:
afterRemove()办法
之前,咱们先来做两件工作:- 1、在
afterRemove()办法中
增加一个参数,稍后再解说为什么
- 2、复习二叉查找树删去的代码逻辑
- 1、在
(1)几种简略的状况
- 先来看看这几种状况:
- ①被删去的节点是赤色
- ②BST度为1节点
- ③真实删去的是根节点
- ④BST”度为2“的节点
- 看我列了这么多,是不是有些… 别着急,你看看这几种状况的完成,就知道有多简略了
- 这几种状况能够说是最简略的状况了
- ①:删去的是赤色节点,不解说了
- ②:假如传进来用于替代的子节点是赤色,阐明该节点必定是度为 1 的节点嘛,看看删去的代码即可知道
- ③:取出父节点,若发现父节点是空的。阐明只要一个根节点,你把仅有的节点都删去了,直接回来就行了呗(当然,之后可能有其他状况,可能实际不只要一个节点,可是从逻辑上是,也能够满足)
- ④:???都没看到这种状况啊?确实是,代码都不用写,由于真实被删去的节点在B树的最终一层,度又为2,就只能有一种状况了,再看看上面总结~
(2)下面评论的状况都是删去的是BST中度为0的节点
- 经过了上面的四种状况,能来到这儿阐明:
- 在二叉查找树中:删去的是度为0的节点
- 在等价的4阶B树中:删去后会产生下溢现象
- 在红黑树中:删去的是黑色的节点,且它自己一个子节点都没有
- 经过上面的剖析和总结,来到这儿咱们需求取出兄弟节点来评论
- 怎么取出兄弟节点呢?和之前相同
node.sibling()
吗? - 其实不然,咱们来看看,传入
afterRemove()办法
中节点的大致内存细节
- 能够发现,父节点对自己的引证都现已断掉了,只要自己内部还引证着父节点
- 那咱们来看看
node.sibling()
办法,还能拿到真实的兄弟节点吗?
- 看了取兄弟节点的逻辑,是不是发现,根本拿不到兄弟节点了。那该怎么拿呢?
- 来到这儿删去的必定是BST中度为0的叶子节点,而删去叶子节点,势必会铲除父节点对自己的引证
- 对父节点来说:要么是
parent.left
会清空,要么是parent.right
会清空,反过来想想 - 已然删去的节点在父节点的哪一边,那儿就会被清空。那咱们就能够依据哪边是空的,逆推出删去的是左面,还是右边。假如是
parent.left == null
,那么兄弟节点便是:parent.right
。反之亦然
- 兄弟节点找到了,直接开端判别吗?
- 回看上面的剖析,咱们评论的时分,都是用被删去的节点坐落右子树来举例的。其间也提到了,若删去的节点在左面,关于左右操作而言,是完全对称的。并且怎么染色也是共同的
- 由此,就能够只完成一边,咱们以上面的例子,坐落右子树为例。另外一边只需求修正写好后的左右即可
① BST兄弟节点是赤色
- 也便是咱们所说的,关系有些杂乱的一种状况,咱们需求将其转化成了解的状况,之后再共同处理
- 咱们无非是想要将兄弟的子节点(侄子),变成待删去节点的兄弟,所以这儿需求经过右旋,就交流了节点
- 可是还需求维护红黑树的性质,所以将其原先的兄弟节点染成【黑色】,父节点染成【赤色】
- 最终别忘了要将新的兄弟节点:
sibling 赋值
② BST兄弟节点是黑色
- 经过上面的操作,成功将兄弟节点是赤色的状况转化成了黑色,下面即可共同处理
- 也便是说,来到这儿,兄弟节点必定是黑色,那咱们就能够依据兄弟是否有赤色的子节点来评论了
1、至少有一个赤色的子节点
- 这种状况,兄弟节点很殷实,至少能够借一个给我,那就借一个给下溢的兄弟呗
- 经过旋转来借节点的时分,会有三种状况:
左左、左右、左左或左右
,并且旋转完成后,都需求染色来维护性质,那咱们能够先处理一些特殊状况:左右
,之后就能够共同当作左左
的状况了
- 经过上面的处理后:
-
LR
的状况变成了LL
,而有两个赤色孩子的状况左左或右右
,本身就能够看做是LL
- 所以能来到下面的剖析,必定都能够当作
LL
的状况
- 也便是需求
旋转 + 染色
- 由于是LL,所以将父节点右旋即可
- 旋转后兄弟节点变成了新的中心节点,将中心节点承继旧父节点的色彩,再将中心节点的左右两头都染成黑色即可
- 至于为什么要承继旧父节点的色彩,在上篇文章现已剖析过了,能够结合前面的内容,剖析剖析哟~
2、一个赤色的子节点也没有
- 最终一种状况,兄弟也借不了,只能向父节点求助了,也便是
经过染色将父节点和兄弟节点兼并
- 实际上是经过染色解决的下溢,这儿就不多解说了
- 还有一点值得注意的是:染色前,需求记载父节点的色彩
- 假如原先是赤色,阐明父节点向下兼并后,父节点并不会产生下溢现象,不需求额外处理
- 可是假如原先是黑色,本身就只要一个黑节点了,向下兼并后,父节点也将会出现下溢
- 这儿的处理逻辑,便是将父节点作为被删去的节点,递归履行删去后的逻辑
- 所以,当咱们将父节点传入
afterRemove()
办法之后,其间有一处需求调整:
- 由于咱们只是是将父节点作为被删去的节点,所以它履行代码前,左右子节点的引证必定不会发生变化,所以检查是否是左子节点,就得用曾经的办法
(3)对称状况的处理(被删去的节点在左面)
- 在上面,咱们现已剖析且完成了被删去节点在右边的一切状况。真的谢了✌️✌️
- 一向说它和在左面是对称的。详细有多对称呢?
- 看我标赤色的当地,便是交流左右即可
- 左面取左,右边就变成取右
- 左面右旋,右边就左旋
- 当然,有些当地交流了也是相同的效果,就没有交流
(4)优化afterRemove()
的参数
- 看完了上面的完成,真的能够说是苦尽甘来啊!!!
- 虽然完成了,咱们之前留了一个问题:
afterRemove()办法
中增加了一个参数
-
并且这个参数,在整个完成逻辑中,仅在这儿用到了
-
还有在AVL树的完成中,其实也没有用到这个参数
-
那咱们能否做到,不要这个参数,共同参数呢?
-
最初多传这个参数,是想在红黑树这儿方便判别删去的是度为1的节点
-
也便是需求找到用于替代删去节点的节点。其余的当地都不需求改动,只是需求处理上面代码的逻辑
-
处理前,咱们先看看,在二叉查找树中,应该怎么传入参数,这是一个值得思考的问题
-
先来看看度不是0的状况,改回之前的完成即可【箭头指向的是修正后的代码】
- 仅有要修正的便是下面度为1的节点
- 由于
AVL树和红黑树
共用了一个删去模板。所以在考虑传入红黑树的参数时,还得确保AVL树
删去后的处理是正常的 - 之前在完成
AVL树
的时分,这儿是将被删去节点node
传入了afterRemove()办法
- 而现在传入的是
用于替代的child
,那么咱们先看看AVL
树的完成
-
能够发现,其实并没有影响,这儿需求拿到被删去节点的父节点就能够。而咱们在传入
child
的时分,本身就现已将被删去节点的父节点赋值给child.parent
了,逻辑也不需求修正 -
所以并不会产生影响,那红黑树中呢?
-
咱们先来修正,再解说:
- 修正这儿,应该很好理解,由于传进来的就只要一个参数了
- 便是将上面剖析的①②种状况,兼并成共同的逻辑了。看看注释的内容
(5)完好完成afterRemove(Node<E> node)
protected void afterRemove(Node<E> node) {
/*
①、被删去的节点是赤色的状况
②、删去的节点有且仅有一个赤色的子节点【也便是在二叉查找树中删去的度为 1 的节点,找到替代它的子节点】
*/
if (isRed(node)) {
/*
①:假如被删去的节点是赤色的状况,将它染黑也不要紧,横竖它的内存马上就要释放掉了
②:将用于代替被删去节点的子节点染成黑色即可
*/
black(node);
return;
}
Node<E> parent = node.parent; // 取出被删去节点的父节点
// ③、假如父节点是空的,阐明删去的是根节点
if (parent == null) return;
/*
阐明曾经删去的是左面
1、(parent.left == null)是被删去的节点
2、(node.isLeftChild())是父节点下溢
*/
boolean isLeft = parent.left == null || node.isLeftChild();
// 左面为 null 阐明右边是兄弟节点,否则是左面
Node<E> sibling = isLeft ? parent.right : parent.left;
if (isLeft) { // 被删去的节点在左面,与下面的操作对称
// 兄弟节点是赤色
if (isRed(sibling)) { // 将其转化为兄弟节点是黑色的两种状况
rotateLeft(parent); // 左旋,改换兄弟节点
red(parent); // 父节点染成赤色
black(sibling); // 兄弟节点染成黑色
sibling = parent.right; // 兄弟节点变了,改动引证
}
// 能来到这儿,兄弟节点必定是黑色的了
if (isRed(sibling.right) || isRed(sibling.left)) { // 兄弟至少有一个赤色的子节点
if (isRed(sibling.left)) { // RL的状况,将其转化为RR,与下面共同处理
rotateRight(sibling); // 兄弟节点右旋
sibling = parent.right; // 兄弟节点变化了
}
// 能来到这儿,阐明都能够当作是RR的状况了
rotateLeft(parent); // 将父节点左旋
/*
1、兄弟节点变成了新的父节点(新的中心节点)
2、将新父节点的色彩承继旧父节点的色彩
3、将新父节点的左右子节点都染成黑色
*/
color(sibling, colorOf(parent));
black(sibling.left);
black(sibling.right);
} else { // 兄弟一个赤色的子节点都没有
boolean isBlack = isBlack(parent); // 记载父节点原先的色彩
// 将父节点染成黑色,兄弟节点染成赤色
black(parent);
red(sibling);
if (isBlack) { // 假如父节点原先便是黑色的
afterRemove(parent); // 阐明向下兼并后,它也会下溢,将它作为被删去的节点
}
}
} else { // 被删去的节点在右边,与上面的操作对称
// 兄弟节点是赤色
if (isRed(sibling)) { // 将其转化为兄弟节点是黑色的两种状况
rotateRight(parent); // 右旋,改换兄弟节点
red(parent); // 父节点染成赤色
black(sibling); // 兄弟节点染成黑色
sibling = parent.left; // 兄弟节点变了,改动引证
}
// 能来到这儿,兄弟节点必定是黑色的了
if (isRed(sibling.left) || isRed(sibling.right)) { // 兄弟至少有一个赤色的子节点
if (isRed(sibling.right)) { // LR的状况,将其转化为LL,与下面共同处理
rotateLeft(sibling); // 兄弟节点左旋
sibling = parent.left; // 兄弟节点变化了
}
// 能来到这儿,阐明都能够当作是LL的状况了
rotateRight(parent); // 将父节点右旋
/*
1、兄弟节点变成了新的父节点(新的中心节点)
2、将新父节点的色彩承继旧父节点的色彩
3、将新父节点的左右子节点都染成黑色
*/
color(sibling, colorOf(parent));
black(sibling.left);
black(sibling.right);
} else { // 兄弟一个赤色的子节点都没有
boolean isBlack = isBlack(parent); // 记载父节点原先的色彩
// 将父节点染成黑色,兄弟节点染成赤色
black(parent);
red(sibling);
if (isBlack) { // 假如父节点原先便是黑色的
afterRemove(parent); // 阐明向下兼并后,它也会下溢,将它作为被删去的节点
}
}
}
}
- 至此,终于将红黑树的删去剖析完成了
- 假如你真的耐心的看到了这儿,那和我一同击个掌怎么样~ 🤚🤚🤚
写在后边
- ✌️✌️✌️完好代码
- 推荐阅览:
- 了解二叉查找树 ->《二叉查找树的完成与剖析》
- 了解树的旋转 ->《透过AVL树的完成,学习树的旋转》
- 了解上溢现象 ->《你心里有B树吗?》
- 红黑树的增加 ->《红黑树增加的剖析与完成》
本篇收获
- 加强红黑树删去的剖析
- 能够用代码完成红黑树的删去逻辑
- 复习树的旋转、B树的性质、红黑树的性质