在学习红黑树前,个人建议先学习下 B 树,尤其需要了解 4 阶 B 树的添加、删除,在本博文后续讲解中会大量使用,可参考[C++ 系列] 79. 详解B树搜索、添加、删除
红黑树也是一种自平衡二叉搜索树,以前也叫做平衡二叉 B 树 ( Stmmertic Binary B-tree )。
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩
倍**,因而是接近平衡的。
红黑树没有 AVL 树中 平衡因子 的概念,那么它是如何保持平衡的呢?
若一颗树是 红黑树 的话一定要满足如下 5 条特点:
在这的叶子节点是想象出来的 空节点,即上图的 null 黑色 节点,并不为上图的25、74、78、86、90这几个节点。
眨眼一看好像满足红黑树的各项性质,所以节点仅有红、黑两种颜色,且根节点为黑,任一路径上没有两个相邻的红色节点,并且从根节点到达叶子节点的黑色数量都为 3,它好像就是一颗红黑树?
在这的叶子节点是想象出来的 空节点,即上图的 null 黑色 节点,故能够发现:55–>38–>null(到达叶子节点) 仅经过了 2 个黑色节点,二其它线路都为 3 个黑色节点,故其不为 红黑树。
将下图左边的 红黑树 红色节点上升一层得到右边的这棵树?
仔细观察下,右边这棵树不就是颗 B 树吗?并且还是个 4 阶 B 树,将其转换为更加形象的样子,见右下图:
注意:后面展示的红黑树都会省略 NULL 节点
如果上图最底层的 BLACK 节点是不存在的,则整颗 B 树只有 1 个节点,而且是超级节点。
17 就是 33 的兄弟节点,共同的父节点为 25。
17 与 33 的叔父节点就是 46,50 的叔父节点就是 25。
17、33、50 的祖父节点为 38。
现在看到红黑树一定要做到心中有4阶 B 树,会很大方便理解,并且下面的讲解也是依据 B 树的转化进行。
已知:
添加新节点只可能加到最后一层的叶子节点上,那么最后一层的叶子节点有多少种情况呢?
其实上图已经给出了所有情况:黑、红黑、黑红、红黑红,由于是 4 阶 B 树,故出现且仅出现这些情况。
那么在这些叶子节点中进行插入,排除一些不能插入的错误情况类似于 25 虽然在 B 树中为叶子节点,但在 红黑树 概念中其有左右孩子了,故将其排除掉。同理可排除其他不可插入情况,得到如下的 12 种插入情况:
其中有 4 种情况满足红黑树的性质 4,其父节点 BLACK,同样也满足 4 阶 B 树的性质,因此不需要做任何额外处理,见下图所示:
其中有 8 种情况不满足红黑树的性质 4:parent 为 RED(Double Red),其中前 4 种属于 B 树节点上溢的情况,见下图所示:
其它 6 种情况暂且不论,先看看下图这两种情况:
即 50–>52、72–>60 违背了性质四。首先站在 B 树的角度来思考,52、60 加入必然要成为一个 B 树节点,那么 回想一下 B 树节点是怎么形成的呢?B 树节点是由一个黑色节点与他的红色子节点形成的, 那么在此若 52 想成为 B 树节点,那么 50 就需要变成黑色,并且 46 变成 红色,才能合并到一起变为 B 树节点。
此时会发现,红38–>红46 岂不是两个红色相连了?在这的直观思想是让 红38–>黑50,不就能解决了吗?再仔细观察,这是不是 AVL 树中的 RR 情况,针对 46 进行左旋转,让 50 成为子树的根节点,46 成为子树的子节点,就万事大吉了!
同理,将 72 染成黑色成为 B 树节点,76 需要染成红色,并且现在是 LL 情况,仅需对 76 进行右旋转,让 72 成为子树的根节点,76 成为 72 的子节点。
转换效果图如下图所示:
◼ 判定条件:uncle 不是 RED
LL:grand 右旋转
RR:grand 左旋转
上述两种情况搞定后,再看看下图这两种情况:
有了上面的基础可以知道,46–>50–>48是 RL 情况,76–>72–>74 是 LR 情况。
那么针对46–>50–>48是 RL 情况,就先让 50 做右旋转,再让 46 做左旋转,现在 48 上去成为这颗子树的根节点,那么就需要对 48 将其染为 黑色,左右变成红色。
转换效果图如下图所示:
◼ 判定条件:uncle 不是 RED
LR:parent 左旋转, grand 右旋转
RL:parent 右旋转, grand 左旋转
判断 uncle 颜色是否为 RED,如果不为 RED 就是上面四种情况,如果为 RED 则为下述 4 种情况。
经过上面情况的区分,来看一种新的情况,上溢 – LL,如下图所示:
首先从 B 树角度出发,25、17、33、10 明显产生了上溢情况,因为 4 阶 B 树最多只能存 3 个数据,现在产生了上溢。
解决方法就是跳出最中间的向上合并,然后进行分裂即可。在这 17、25 都算是中间的元素,在这就挑 25 作为中间节点向上合并,因为其颜色刚好搭配,在这不要思考 25 向上合并后的各种情况,比如 38 作为 B 树根节点一定需要变为黑色,而相邻节点变为红色等系列的问题,先来考虑 17–>10、33 小方面的针对 B 树情况进行考虑。
17、33 由于 25 的向上合并,都要变为 B 树节点,在这就需要将其变为黑色。
那么一个小总结就出来了,若是 上溢 – LL 情况,即需要将其父节点、叔父节点均变成黑色即可。
最后来考虑祖父节点 25 的向上合并,本来 38、55 这个位置只有两个节点,现在 25 向上合并,就相当于对 38、55 进行新添加节点 25 的操作,那么就是依然是插入的 12 种情况,将其变成红色配对情况即可,这是一个递归的操作!效果如下图所示:
但若祖父节点 25 持续上溢到根节点的话,并且此时根节点也触发了上溢的条件的话,即下图的情况。此时又需要找中间的元素,在下图中就是 38、55 这两个情况,只需要将根节点转成黑色即可。
这个上溢的 LL 情况,只需要进行染色即可,根本没进行旋转的操作。
◼ 判定条件:uncle 是 RED
顺理成章的又出来了一种新的情况,上溢 – RR,如下图所示:
有了上面 5.6 的经验,现在的操作就简单很多了。将 36 的祖父节点 25 拿出来染成红色,当做一个新的节点,实行插入操作的向上合并。将 17、33 的父节点、叔父节点染成黑色,此时 17、33 就独立成了一个 B 树节点,其它的都与之前的逻辑一致了。直接看效果图如下:
◼ 判定条件:uncle 是 RED
倒数第二种情况了,上溢 – LR,如下图所示
此时 20、17、25、33 依然发生上溢,同理祖父节点 25 需要进行向上合并,叔父节点染成黑色即可。效果图如下:
◼ 判定条件:uncle 是 RED
最后一种情况,上溢 – RL,如下图所示:
现在的话道理都是一样的了,直接效果图如下:
◼ 判定条件:uncle 是 RED
站在 4 阶 B 树的角度上看,红黑树添加共有 12 种情况,其中 4 种情况符合红黑树定义,不需要进行修复调整,而其余八中情况可分为如下三大情况:
同样对比 4 阶 B 树我们知道,在 B 树中真正从内存中被删除的节点元素都在叶子节点中。那么就有下面几种情况。
红黑树结构图如下:
假如删除红色 17、33、50、72 删除一个或多个的话,对红黑树没有影响,它依然是一颗 红黑树,并且是一颗 4 阶 B 树。
拥有 2 个 RED 子节点的 BLACK 节点,在此即 25 节点。25 节点的度为 2, 而在二叉搜索树当中,一定是不可能删除一个度为 2 的节点的,同时一定会找到它的 前驱 / 后继 进行删除。不可能被直接删除,因为会找它的子节点替代删除。因此不用考虑这种情况
拥有 1 个 RED 子节点的 BLACK 节点,在此即 46、76 节点
BLACK 叶子节点,在此即 88 节点
因为情况 1 不必考虑,那么删除情况就仅有拥有1个RED子节点的BLACK节点、BLACK 叶子节点这两种情况了。
红黑树结构如下图:
加入现在要删除 46 、76 的情况,那怎么判定是这种情况呢?仔细观察可发现,它是度为 1 的节点,在二叉搜索树中,就需要找到他的子节点进行删除即可,即将 46 删除 并将 55–>50,将 76 删除 并将 80–>72,即效果图如下:
即为:
这样删除明显不符合红黑树的性质啊,如果从 B 树角度出发,将 46、76 进行直接删除,那么 50、72 独立成为 B 树节点,那么 B 树节点一定是黑色的。
◼ 判定条件:用以替代的子节点是 RED
红黑树结构图如下:
在这中情况下就是删除 46、88 节点,在 BST 树角度上,就是直接被删除,但在 4 阶 B 树中就发生了下溢,因为 4 阶 B 树保证节点的数据最少要有 1 个,最多为 3 个。在这进行了 88 的删除的话,自然发生下溢情况。
在 B 树中处理下溢情况的方法就是,看看兄弟节点是否能借出一个节点,在这里先考虑能借出节点的三种情况如下图:
通过观察能够发现这三种情况下,它的兄弟节点都有一个红色孩子节点,这样兄弟节点就至少有两个节点了,才能借出一个节点。并且兄弟节点的颜色一定是黑色,如果它的兄弟节点为红色的话,那么这个兄弟节点一定是在 父节点 里面的。我们能够知道如果父节点为红色,那么兄弟节点就不能再为红色,即为黑色,如果父节点为黑色,根据红黑树与 4 阶 B 树等价原则,父节点与他的所有子节点构成一个 B 树节点,那么在 B 树中这个红色兄弟节点是在父节点上的。
就如下如图所示,55 作为父节点怎么可能把节点借出来呢?我们需要借的是左右临近的红色节点。
现在见上图,如果删掉 88,76 兄弟给借红色子节点 78 来处理下溢问题,那么就是 80 作为父节点下去到 88 的位置,在兄弟里面挑一个,尽量挑个居中的作为父节点,即 78 上去作为新的父节点
并且还能够发现上图这个情况其实是从 80–>76–>78 d的 LR 情况,首先对 76 进行左旋转,那么 78 就上去了,再对 78 做右旋转 那么 78 就上去了,80 就下去了,下溢情况调整完毕。
通过旋转之后新的父节点要继承原有父节点的颜色,并且它的左右节点需要变成独立的 B 树节点,那么就需要将其左右节点全部染成黑色。因为 80 旋转下去就它一个,肯定为独立的 B 树节点,76 作为它的兄弟节点也一定为 黑色 并且也是独立的 B 树节点。
到现在,删除 – BLACK叶子节点 – sibling(兄弟节点)为BLACK 的情况这样才算调整完毕,接下来再看几个例子加深理解:
删除 88 节点,现在就是很明显的从 80–>76–>72 的 LL 情况,直接对 80 节点进行右旋转即可。并且旋转过后,76 继承 80 的颜色,并将 76 的左右节点染成黑色。效果图如下:
再来个例子:
删除 88 节点,现在就是很明显的从 80–>76–>72 的 LL 情况,从 80–>76–>78 的 LR 情况,在这就选择 LL 对 直接对 80 节点进行右旋转即可,因为这样只需要旋转一次,而 LR 却要进行两次旋转。并且旋转过后,76 继承 80 的颜色,并将 76 的左右节点染成黑色。效果图如下:
如果按照 LR 来进行两步旋转的话,结构和上面的又不太一致,因为一开始对 76 进行左旋转,78 上去,再对 78 进行右旋转,78 作为新的父节点,80 下去解决下溢问题,再将 78 染成原来的父节点颜色,左右染成黑色即可,效果图如下:
所以红黑树的删除,删完之后可能出现多种树的结构,可能都是满足情况的正确答案。
◼ 判定条件:BLACK 叶子节点被删除后,会导致B树节点下溢(比如删除88),此时如果 sibling 至少有 1 个 RED 子节点
红黑树效果图如下:
依旧删除 88,此时它的兄弟节点没有红色子节点,发生了下溢的情况,那么就需要用到 B 树的 合并 方式来处理下溢的情况了。合并是从父节点 80 下来和 76 合并到一起,那么父节点下来与它的子节点融合到一起成为 B 树节点,此时就需要对父节点染成黑色,将兄弟节点染成红色即可。效果图如下:
如果父节点是黑色的情况呢?如果父节点下来,B 树节点必然产生下溢情况。那么要是父节点旁边出现红色节点呢?是不是就不会产生下溢情况了?
答案是不可能再出现红色节点的,因为删除节点为黑色叶子节点,并且它的兄弟节点仍是黑色节点,此时父节点的 left、right 都已经有所指了,自然不会存在再指向一个红色节点的情况。如下图:
其实这个情况处理很简单,只需要将父节点当做被删除的节点即可,也是一个递归的调用。很类似于处理上溢问题的递归调用。
◼ 判定条件:sibling 没有 1 个 RED 子节点
在此黑色的叶子节点的两种情况已经讲完了,下面来看最后一种情况,红黑树结构图如下:
依旧删除 88,在 B 树中就产生了下溢的情况,还得看 76 能不能借节点,如果能借就借一个节点,如果不能借就让父节点下来合并解决下溢问题。但是在这就比较麻烦,现在 88 的兄弟是 55 ,55 的儿子才是 76,就多跨了一层关系,现在为了解决这个问题,我们强制让 76 做 88 的兄弟,那么就又回到了黑兄弟的情况了。
那么思想就很简单了,就是单纯的让 80 指向 76 就好了。见上图可知道可以通过 LL 情况实现,让 80 进行右旋转,此时 55 上去成为新的父节点,80 指向 55 的右孩子 76 节点,而 55 的右边指向 80 。再将删除节点的兄弟节点即 55 节点染成黑色,父节点染成红色即可,效果图如下:
于是又回到了兄弟节点为黑色的情况,即 7.5 的情况。如果将 88 删掉,产生下溢,父节点向下合并,成为 B 树节点,即将 80 染黑,76 染红即可,效果图如下:
再来举一个例子:
依旧删除 88 节点,需要让 80 的左边指向 76,并将父节点染成红色,兄弟节点染成黑色:
回到兄弟节点为黑色的情况了,按照 80–>76–>78 进行 LR 两步旋转的话,一开始对 76 进行左旋转,78 上去,再对 78 进行右旋转,78 作为新的父节点,80 下去解决下溢问题,再将 78 染成原来的父节点颜色,左右染成黑色即可,效果图如下:
◼ 判定条件:sibling 是 RED
与 BST 树一致真正删除的节点肯定是删除的叶子节点,那么以待删除节点的颜色来考虑,若为红色,则直接删除即可,不需要作任何调整,若为黑色,则分以下三种情况:
拥有 2 个 RED 子节点的 BLACK 节点,不可能被直接删除,因为会找它的子节点替代删除。因此不用考虑这种情况
拥有 1 个 RED 子节点的 BLACK 节点
BLACK 叶子节点
于是就只有情况2、3 需要我们来讨论,即有:
最初遗留的困惑:为何那5条性质,就能保证红黑树是平衡的?
回答:那5条性质,可以保证 红黑树 等价于 4阶B树,在这就不证明了,有兴趣coder可自行证明,或者查阅相关资料
◼ 搜索: O ( l o g N ) O(logN) O(logN)
◼ 添加: O ( l o g N ) O(logN) O(logN), O ( 1 ) O(1) O(1) 次的旋转操作
◼ 删除: O ( l o g N ) O(log N) O(logN), O ( 1 ) O(1) O(1) 次的旋转操作
AVL树:
红黑树:
平衡标准比较宽松:没有一条路径会大于其他路径的2倍
最大高度是 2 ∗ l o g 2 ( n + 1 ) 2 ∗ log_2(n + 1) 2∗log2(n+1)( 100W个节点,红黑树最大树高40)
搜索、添加、删除都是 O ( l o g 2 N ) O(log_2 N ) O(log2N)复杂度,其中添加、删除都仅需 O ( 1 ) O(1) O(1)次旋转调整
搜索的次数远远大于插入和删除,选择 AVL 树;搜索、插入、删除次数几乎差不多,选择红黑树
相对于 AVL树来说,红黑树牺牲了部分平衡性以换取插入/删除操作时少量的旋转操作,整体来说性能要优于 AVL 树
红黑树的平均统计性能优于 AVL 树,实际应用中更多选择使用红黑树