红黑树的删除
代码实现见GitHubgithub.com红黑树插入见(一)
mathnull:红黑树插入、删除、实现与绘制(一)zhuanlan.zhihu.com直观上,按非空孩子节点的数量分,可以将要删除的节点分为三种情况:2个孩子节点,1个孩子节点,没有孩子节点。如果删除节点没有孩子,例如要删除上图的节点7,那么可以直接将其删除。如果删除节点只有1个孩子,例如要删除上图的节点37,由于节点37只有1个左孩子,可以把32->37->34看成一个链表,所以可以直接对节点37进行删除,然后更新节点32与节点34之间的指向。如果删除节点有2个孩子,例如要删除上图的节点32,假如我们直接删除节点32,会出现父节点45只能指向删除节点的其中一个孩子节点,另一孩子节点发生断裂的情况,所以到此还没结束。要解决这个问题很自然想到放一个新节点在删除节点的位置,当然不是随便一个节点都可以,他必须是承上启下的,即比删除节点的左子树大,比删除节点的右子树小(可以推出比父节点小)。不难想到删除节点的后继节点(当然前置节点也行),如果我们把后继结点放在删除节点的位置,就解决了删除节点32引起的结构问题。但是,我们把后继结点放在删除节点的位置,说明我们把后继结点删除了,会不会引起新的结构问题呢,注意到后继结点的左孩子必空(否则他不是后继结点),即后继结点最多只有1个孩子,按照前面的讨论,后继结点是可以直接删除而不会引起结构上的问题。于是删除有2个孩子的节点只要把值改成后继结点的值,然后删除后继结点。所以最终分为两种情况:如果删除节点有0或1孩子则直接删除,如果删除节点有2个孩子则转化为删除其后继节点(注意:在树的结构上只删除了后继节点)。
上述讨论解决了删除节点时产生的结构问题,但是删除操作很可能会引起路径上黑色节点数量的改变。如果删除删除节点是红色的,那么没有问题,直接返回,如果删除节点是黑色的,那么需要调整。
首先,我们细化一下删除节点只有一个孩子的这种情况,按照红黑树的定义,如果一个节点只有一个孩子,可以推出节点必然是黑色的,孩子必然是红色的。反证法:假如节点是红色的,然后他又只有一个孩子,不妨设为左孩子,第一这个孩子不可能是红色,如果这个孩子是黑色,那么从红色节点出发到空指针的路径上黑色节点数量不相等,所以节点只能是黑色。同理易证孩子是红色。如果我们删除的是这种情况下的黑色节点,那么把他的孩子改成黑色即可,例如删除上图的节点37。注:前文论述中的孩子是非空的,下文开始的论述中包括孩子为空指针(黑色)的情况
已知被删除的节点是黑色,可以推出删除节点的兄弟非空,且兄弟子树中至少存在一个非空黑色节点(否则删除节点之前父亲节点至空指针路径上的黑色节点数量不一致),先来看兄弟节点是黑色的情况。
目前已知被删除节点是黑色的,兄弟节点是黑色的。思路与插入节点是一样的,一是如果兄弟节点的孩子有红色的,那么把他转移过来并改成黑色节点,这样就弥补了删除导致的缺少一个黑色节点;二是兄弟节点那边也删除一个黑色节点,即把兄弟节点改成红色的,此时,兄弟节点子树和被删除节点子树中路径上的黑色节点数量是相等的,因为大家都减去了一个黑色节点,所以从父亲节点到空指针路径上的黑色节点减少了一个,为了简化问题,我们把被删除节点子树和兄弟节点子树都抹去,等价于原本在父亲节点的位置有一个黑色节点被删除了(正常删除一个黑色节点会使得从被删除节点到空指针路径上的黑色节点减少一个),所以问题转化为了父节点被删除,容易想到递归,递归的终止条件是被删除节点所在位置的节点是根节点或者红色的。这里解释一下“被删除节点所在位置的节点”,我们在进入调整之前,已经把要删除的节点从树中移除了,所以删除节点的位置被其孩子(可空)占据,而孩子颜色是未知的,但我们清楚的知道原本那个位置是一个被删除的黑色节点(否则我们不会进入调整),所以如果代替的孩子是红色的,那么把他改成黑色就相当于在那个位置先减1再加1,等于没有改变路径上的黑色节点数量,递归可以终止。
思路一只有当兄弟节点至少有一个红色孩子节点才能进行,思路二只有当兄弟节点的孩子都是黑色的才能进行。下面展示被删除节点是父亲节点的左孩子情形,为右孩子时基本类似。BBB型
第一个B表示兄弟节点为黑色,第二个B表示兄弟节点的左孩子为黑色,第三个B表示兄弟节点的右孩子为黑色。我们要删除的节点是图a中的13,把13删除得到图b,接着把兄弟节点16改成红色得到图c,递归得到图d。BXR型
第一个B表示兄弟节点为黑色,第二个X表示兄弟节点的左孩子颜色任意,第三个R表示兄弟节点的右孩子为红色。由图a删除节点1得到图b,接下来想办法把红色的右孩子转移到左边并改成黑色,注意并非真正转移节点18,只要达到等价效果即可。先把兄弟节点15改成父亲的颜色,然后把父亲节点和右孩子改成黑色,得到图c,然后对父节点左旋得到图d(也可以先旋转再改色,此处对父节点的颜色没有要求)。BRB型
第一个B表示兄弟节点为黑色,第二个R表示兄弟节点的左孩子为红色,第三个B表示兄弟节点的右孩子为黑色。图a删除节点1得到图b,我们的思路依然是想办法把红色节点转移到左边去改成黑色,看到图b很容易想起插入时说的RL型(结构上),处理上也是类似的,先对兄弟节点右旋,然后交换颜色(先交换颜色再右旋也是一样的),由此得到图d。注意到图d是BXR型,剩下的按BXR型处理即可。RBB型
接下来是兄弟节点为红色的情况,可以推出兄弟节点必有两个非空黑色孩子节点,以及父节点是黑色的。下面展示被删除节点是父亲节点的左孩子情形,为右孩子时基本类似。
图a删除6可得图b,我们的想法是红色节点转移到左边,转移只需将父节点31左旋,把父节点31改成红色,兄弟节点38改成黑色。但是此时我们不能把转移到左边的红色节点31改成黑色,理由如下:假如我们把节点31改成了黑色,那么此时从节点38经节点35到空指针路径上的黑色节点数量为4,而从节点38经节点43到空指针路径上的节点数量为3,意味着不仅原本的问题没有解决(38经31到空指针路径上的黑色节点数量为3),现在还产生了新的问题,这种情况下要修正必须在节点31的右子树(原本是在旋转之前兄弟节点38的左子树,他未必只有一个节点)中删除(颜色数量上的减少,并非结构上的删除)一个黑色节点,这可能是一个根本无法实现的事,例如35有两个红色孩子。所以应该保持转移过来的节点为红色,问题转化为删除节点(31的左孩子)与新的兄弟节点(原本兄弟节点的左孩子,必黑)之间黑色节点数量不平衡的问题,因为兄弟节点为黑色的情况我们已经全部讨论过了,接下来按上述讨论的方式处理即可。上图中图d便是BBB型。
删除图a中的节点6,上图中的图d便是BXR型。
删除图a中的节点6,上图中的图d便是BRB型。
可视化参考了南浦月
实现参考了《算法导论》