教育改变生活

标题: 图解:什么是红黑树?(二) [打印本页]

作者: 李玮    时间: 2020-11-10 15:12
标题: 图解:什么是红黑树?(二)
再看红黑树
来看它的五条定义:
1.节点颜色有红色和黑色
【2-3树到红黑树的转化已经解释过】
2.根节点必为黑色
【2-3树中如果根节点为2节点,那么它本来就对应红黑树中黑节点;如果根节点为3节点,也可以用黑色节点表示较大的那个元素,然后较小的元素作为左倾红节点存在于红黑树中】
3.所有叶子节点都是黑色
【此处提到的叶子其实是空链接,因篇幅问题不便全部画出】
####4.任意节点到叶子节点经过的黑色节点数目相同
【红黑树中的红节点是和黑色父节点绑定的,在2-3树中本来就是同一层的,只有黑色节点才会在2-3树中真正贡献高度,由于2-3树的任一节点到空链接距离相同,因此反应在红黑树中就是黑色完美平衡】
5.不会有连续的红色节点
【2-3树中本来就规定没有4节点,2-3-4树中虽然有4节点,但是要求在红黑树中体现为一黑色节点带两红色儿子,分布左右,所以也不会有连续红节点】
相信在你的视角中,红黑树已经不再是这五条僵硬的定义了,它背后正浮现着一颗2-3树概念模型。虽然你已经有了这样的认识,但是红黑树作为真正的实现模型,我们还是要回到这个实现本身来探究它的一系列操作。在开始前,我准备了两个基础知识,希望能帮助到你。
1.作为二叉查找树
二叉查找树的节点有一个元素X和两个指针域,左指针指向小于X的元素,右指针指向大于X的元素。
假设我们的插入序列是1~10,那么这颗树会演变成只有右链接的形式,树高会增加到10层,这个时候已经不具备O(LogN)的查找时间复杂度,因为这颗树退化成了链表。
因此对二叉树进行平衡调整是很重要的一个环节,无论是AVL还是红黑树,它们本质上都是希望尽可能保证这颗二叉查找树中的元素尽量均衡的分布在树的两侧。
当我们向一颗二叉查找树中插入一个元素Y的时候,我们会一直与树中的节点进行大小比较,如果Y小于当前元素,就往左走,如果Y大于当前元素,就往右走,直到达到叶子节点,这个时候我们可以把Y插入这颗二叉查找树了。
由于这次的插入动作,整棵树可能会发生一些不平衡,因此我们需要在插入后进行一次平衡调整,使得整棵树恢复到平衡的状态(具体如何调整,要看树是AVL还是红黑树亦或是其他的平衡树)。
二叉查找树的删除是一个很有意思的问题,不同于插入的是,待删除的元素并不能保证一定出现在树中的叶子节点。这将带来一个棘手的情景,即我们需要从树的中间部分取走一个元素,而且在取走后还需要经过调整来使得整颗树满足平衡的性质。从树的中间部分直接取走一个节点的场景实在是太多,也牵扯到了太多相关的节点,这种操作很难实现。
好在有人提出了一个观点,我们对查找树中一个节点的删除,其实可以不必真的改动这个节点的位置。由于查找树的特殊性质,将某个元素节点删除后,它有两个最佳替代者,分别是有序序列中的前驱元素和后继元素。
我们还是以一个包含元素1~10的二叉查找树为例,如果我们希望删除5所在的节点,那么让4或者6替代它的位置都是可行的。作为前驱元素的4,会存放在5所在节点的左子树的最右侧;作为后继元素的6,会存放在5所在节点的右子树的最左侧。
关于这个结论,大家只需稍加思索便可以明白。
现在我们又让问题简化了,也就是说,删除某个节点的时候,我们先找到它的前驱元素或者后继元素(随便选一个),将它的前驱元素直接填到待删除的节点,然后再把它的前驱元素或者后继元素删除。
这个时候问题就转化成了在二叉查找树中删除一个没有左子树的节点(或者是一个没有右子树的节点),我们只需要将这个节点删除再进行对应的平衡调整即可(虽然还是需要调平,但是比直接在树中层删除一个同时具备左右儿子的节点要容易很多)。
注意,此处并没有强调是针对红黑树的操作,因为红黑树和AVL都是二叉查找树,它们都适用这个方法。
介绍一下树的旋转
为了调平一颗二叉树,使得其左右节点数目分布均匀,通常会选择旋转的手段。你可以把一颗二叉树某节点的左右子树想象成天平上待称量的物品,如果哪边重了,我们就从重的那边拿出一部分,加到轻的那边,以此保持相对的平均。
在二叉树中这种调整的操作就是旋转,下面给出了两个示例,希望大家能够仔细探究,旋转是二叉树调平的精髓。
介绍一下树的旋转
理解了这些之后,再去看红黑树的插入删除,就能够理解旋转和染色背后的意义了。 我们选择算法4中的左倾红黑树作演示:首先看插入
如图所示,对于左倾红黑树的插入一共有三种可能的情况。
在插入时,可以体会到左倾红黑树对于左倾的限制带来的好处,因为在原树符合红黑树定义的情况下,如果父亲是红的,那么它一定左倾,同时也不用考虑可能存在的右倾兄弟(如果有,那说明原树不满足红黑树定义)。
这种限制消除了很多需要考虑的场景,让插入变得更加简单。
左倾红黑树的删除
左倾红黑树的删除需要借鉴上文中提到的二叉查找树通用的删除策略,当我们要删除某个节点的时候选择它的前驱节点或者后继节点元素来替代它,转而删除它的前驱/后继节点。
在这个例子中,我选择用后继节点来替代被删除节点。
假设我们需要删除的节点它的右子树如图所示,那么对该节点的删除实际上转为了对2的删除。
我们从当前的根节点出发,利于2-3树中预合并的策略逐层对红黑树进行调整。具体的做法是,每次都保证当前的节点是2-3树中的非2节点,如果当前节点已经是非2节点,那么直接跳过;如果当前节点是2节点,那么根据兄弟节点的状况来进行调整:
这样的策略能够保证最后走到待删除节点的时候,它一定是一个非2节点,我们可以直接将其元素删除。
接下来要考虑的是修复工作,由于红黑树定义的限制,我们在调整的过程中出现了一些本不该存在的红色右倾节点(因为生成了概念模型中的临时4节点),于是我们顺着搜索的方向向上回溯,如果遇到当前节点具备右倾的红色儿子,那么对当前节点进行一次左旋,这时原本的右儿子会来到当前节点的位置,然后将右儿子与当前节点交换颜色,我们就将右倾红节点修复成了左倾红节点,同时我们并没有破坏黑色节点的平衡。
右倾转左倾是一个很基本的操作,我们以35,44为例,你既可以将35作为黑节点,44作为右倾红色儿子;也可以将44作为黑节点,35作为左倾红儿子。事实上我们对于右倾的修复就是换了一种树形而已。一路回溯到当前根节点,直至路径中不再包含任何的红色右倾节点,至此修复工作全部完成。
总结
这篇文章的目的旨在从概念模型2-3树出发介绍一颗红黑树的前世今生。希望大家能够跳出枯燥的五条定义,更加本质地认识红黑树中的各种操作来源。
虽然本文只是介绍了相对简单的左倾红黑树,但是如果能够将左倾红黑树认识的很清楚,那么普通红黑树也只是多了一些情况而已。
对于还有精力阅读算法导论的读者,我给出一点自己的经验:
写在最后
最后,如果你被问到红黑树,也许你可以试着反问面试官一个问题:“您应该知道红黑树的五条定义,如果我构造一颗只有黑色节点的红黑树,这样子可行吗?因为这样子没有破坏任何一条红黑树的规则。”
如果他回答可行。
继续问:“那么请问红黑树中要红节点干什么呢?红节点的真实意义是什么呢?”
你们的故事就开始了,而我和你的算法故事也才刚开始。






欢迎光临 教育改变生活 (http://bbs.goldoar.com/) Powered by Discuz! X3.2