你要删除的节点x可分为三类:有两个子节点的,只有一个子节点的,以及没有子节点的。由于删除有两个子节点的情况可以转化为后两种情况(找到待删除节点的后继节点y,将该节点的值放在x处,x处的颜色不变,注意这时候既没有破坏二叉搜索树的性质,也没有破坏红黑树关于节点颜色的性质,我们只要接着删除节点y即可,而节点y最多只会有一个子节点。)
只有一个子节点时,用该子节点代替删除节点的位置(保留子节点自己的颜色和值),没有子节点时,直接删除即可。如果删除的节点是红色,那么就万事大吉了, 因为删除一个红色节点不会破坏红黑树关于节点颜色的性质,而上述所有的删除操作也不会破坏二叉搜索树的性质。删除该红色节点后还是一个合法的红黑树。
如果删除的节点是一个黑色节点,那么就需要进行接下来的处理:
由于删除操作,某些路径上会少一个黑色节点。在此我们假设,删除的节点为x,删除x后代替x位置的是节点y(当x没有子节点时,y就是是个null,然而算法导论里将所有unll节点称之为叶节点,并具有黑色颜色,这样处理确实方便了很多,这里和普通的树的定义不同,注意)。
我们假设y节点除了本身的颜色外,另外又携带有一个黑色颜色,这样我们就将某些路径上少一个黑色节点的矛盾等价为某一节点具有两个颜色的矛盾。那么我们如何处理这个拥有两个颜色的节点y呢?
<1.如果y的颜色为红+黑,那么直接去掉红色颜色,不破坏红黑树性质。
<2.如果y是根节点(那么颜色一定是黑+黑),那么直接去掉一个黑色,这时所有路径上的黑色节点都少了1,但都相等,也不破坏红黑树性质。
根据这两条性质,我们需要处理的矛盾节点就是黑+黑色的(只要一碰到红+黑就用情况1处理,完成删除)
接下来开始分类讨论,这里我们不妨假设节点y是其父节点的左节点(相反情况全部对称处理)。
情况1,y的兄弟节点b是红色,那么y的父节点和b的两个孩子都是黑色,这时对父节点做左旋操作并交换父亲节点和b的颜色,目的是让y的兄弟节点变为黑色(旋转后y的兄弟节点变成了b的左孩子,是黑色的)。
情况2,y的兄弟节点b是黑色,b的两个孩子都是黑色,这时我们将y的兄弟节点b设置为红色,并将y携带的黑色传递给它的父亲节点。(对于b的两个孩子来说,其父亲虽然变成了红色,但是其祖父节点额外多了一个黑色,因此路径上的黑色节点个数还是不变。)这时其父亲节点本身的颜色不定,如果本身是红色的,那么用<1. 处理,完事,如果父亲还是黑色的,那么还是落入了现在讨论的这几种情况中,继续处理。
情况3,y的兄弟节点b是黑色,b的左孩子是红色,右孩子是黑色,y的父亲节点颜色不定,这时对b做右旋,并交换b和其左孩子的颜色。目的是将y的兄弟节点的右孩子变为红色。
情况4,y的兄弟节点b是黑色,b的右孩子是红色,左孩子颜色不定,y的父节点颜色不定,这时对y的父亲节点做左旋,并交换y和b的颜色。注意,对于y来说,父亲节点到y的这条路径上会多一个黑色节点,我们刚好用y携带的那个黑色抵消,对于b到其右孩子这条路径上会少一个黑色节点,我们将b的右孩子由红色设置为黑色。完事,黑色被抵消掉了。
(关于父子的描述都是旋转之前的关系,这样更清楚一点。另外稍微分析一下旋转操作之后如何交换节点颜色会保持几个路径上的黑色节点数量不变,这一点应该不难)。
情况1在向情况3和4转变,情况2在向<1, <2和情况3和4转变,情况3在向情况4转变,而情况4条件下通过一次旋转可以抵消掉这个黑色。把握住这个核心逻辑。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。