Try   HackMD

红黑树的删除操作情况繁多,较为复杂。

大多数红黑树的实现选择将节点的删除以及删除之后的维护写在同一个函数或逻辑块中(例如 Wikipedia 给出的 代码示例linux 内核中的 rbtree 以及 GNU libstdc++ 中的 std::_Rb_tree 都使用了类似的写法)。这种实现方式并不利于对算法本身的理解,因此,本文给出的示例代码参考了 OpenJDK 中 [TreeMap] https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/TreeMap.java) 的实现,将删除操作本身与删除后的平衡维护操作解耦成两个独立的函数,并对这两部分的逻辑单独进行分析。

Case 0

若待删除节点为根节点的话,直接删除即可,这里不将其算作删除操作的 3 种基本情况中。

Case 1

若待删除节点 N 既有左子节点又有右子节点,则需找到它的前驱或后继节点进行替换(仅替换数据,不改变节点颜色和内部引用关系),则后续操作中只需要将节点 N 删除即可。这部分操作与普通 BST 完全相同,在此不再过多赘述。

视频链接:删除既有左子节点又有右子节点的节点

注:这里选择的前驱或后继节点保证不会是一个既有非 NIL 左子节点又有非 NIL 右子节点的节点。这里拿后继节点进行简单说明:若该节点包含非空左子节点,则该节点并非是 N 节点右子树上键值最小的节点,与后继节点的性质矛盾,因此后继节点的左子节点必须为 NIL。

Case 2

待删除节点为叶子节点,若该节点为红色,直接删除即可,删除后仍能保证红黑树的 4 条性质。若为黑色,删除后性质 4 被打破,需要重新进行维护。

注:由于维护操作不会改变待删除节点的任何结构和数据,因此此处的代码示例中为了实现方便起见选择先进行维护,再解引用相关节点。

Case 3

待删除节点有且仅有一个非 NIL 子节点,若待删除节点为红色,直接使用其子节点 S 替换即可;若为黑色,则直接使用子节点 S 替代会打破性质 4,需要在使用 S 替代后判断 S 的颜色,若为红色,则将其染黑后即可满足性质 4,否则需要进行维护才可以满足性质 4。

视频链接:删除有左子节点的节点

视频链接:删除有右子节点的节点