承接上一篇AVL树AVL树,红黑树相较于AVL树,就相当于完全二叉树相当于AVL树,如何在性能退化和维护成本之间做出CS中经典的trade-off
文章目录
- 红黑树的概念
- 红黑树查询效率
- 红黑树的插入
- 1 插入节点N为根节点
- 2 插入节点N的父节点P为黑色
- 3 N的父节点P为红色,且叔叔节点U也为红色
- 4 父节点P为红色,叔叔节点U为黑色,P为左孩子,N为右孩子
- 5 父节点P为红色,叔叔节点U为黑色,P为左孩子,N为左孩子
- 红黑树插入总结
- 红黑树的删除
- 1 删除节点X为红色
- 2 替换节点N为根节点
- 3 S为黑,S右孩子为红,N为P的左孩子
- 4 S为黑色,S左孩子为红,S右孩子为黑,N为P的做孩子
- 5 S为红色,其余节点为黑色
- 6 N、P、S、SL、SR全为黑
- 7 P为红,N、S、SL、SR为黑
- 删除总结
- 总结
红黑树的概念
相较于AVL树,通过平衡因子维护一个绝对平衡的二叉树,红黑树采用不严格平衡,从而减少了对树结构的调整,在大量IO的情况下有着更加优秀的性能。
红黑树是根据以下的性质定义,实现自平衡:
- 节点是红色或者黑色
- 根节点是黑色
- 叶节点是黑色(这里的叶节点表示nullptr节点,而非一般的叶节点)
- 每个红色节点的子节点都是黑色的(不能有连续的两个红色节点)
- 从任意节点到其每个叶子的简单路径都包含相同数量的黑色节点(黑高一致)
这里需要注意的是,红黑树的叶节点并非左右子树都为nullptr的节点,而是nullptr节点。
其中相对关键的性质是性质4和性质5,通过这两条性质,我们就可以保证红黑树的搜索效率为O(logn)。
红黑树查询效率
为何通过以上的5条性质就可以保证红黑树的性能不会退化严重,依然可以维持在O(logn),类似于AVL树,我们可以采用数学归纳法进行证明。
首先我们证明一个引理:
以任意节点
x
为根的子树中包含至少
2
b
h
(
x
)
−
1
个内部节点
以任意节点x为根的子树中包含至少2^{bh(x)}-1个内部节点
以任意节点x为根的子树中包含至少2bh(x)−1个内部节点
b
h
(
x
)
为
x
的黑高,即从节点
x
出发到达一个叶节点的任意一条简单路径上黑色节点的数量
(
不包含
x
本身
)
bh(x)为x的黑高,即从节点x出发到达一个叶节点的任意一条简单路径上黑色节点的数量(不包含x本身)
bh(x)为x的黑高,即从节点x出发到达一个叶节点的任意一条简单路径上黑色节点的数量(不包含x本身)
- 当 b h ( x ) = 0 bh(x)=0 bh(x)=0时, x x x为叶子结点,以 x x x为根节点的子树包含0个内部节点,结论成立。
- 考虑一个高度为正值且有两个子节点的内部节点 x x x。每个子节点的黑高为 b h ( x ) bh(x) bh(x)或 b h ( x ) − 1 bh(x)-1 bh(x)−1,这取决于子节点本身是红色 b h ( x ) bh(x) bh(x)还是黑色 b h ( x ) − 1 bh(x)-1 bh(x)−1。因此,以 x x x为根节点的子树,至少包含 2 b h ( x ) − 1 − 1 + 2 b h ( x ) − 1 − 1 + 1 = 2 b h ( x ) − 1 个节点 2^{bh(x)-1}-1+2^{bh(x)-1}-1+1=2^{bh(x)}-1个节点 2bh(x)−1−1+2bh(x)−1−1+1=2bh(x)−1个节点,证明完毕。
另外根据性质4,我们可以知道: 从根节点到叶节点的任意一条简单路径上至少有 1 2 的节点为黑色节点 从根节点到叶节点的任意一条简单路径上至少有\frac{1}{2}的节点为黑色节点 从根节点到叶节点的任意一条简单路径上至少有21的节点为黑色节点即黑高至少为高度的一半,因此,假设一个最糟糕的红黑树,其节点数为n,高度为h,则黑高 b h ≥ 1 2 h bh\geq\frac{1}{2}h bh≥21h,根据引理,我们有 n ≥ 2 b h ( x ) − 1 ≥ 2 h 2 − 1 n\geq2^{bh(x)}-1\geq2^{\frac{h}{2}}-1 n≥2bh(x)−1≥22h−1 故 h ≤ 2 l o g ( n + 1 ) 故h\le2log(n+1) 故h≤2log(n+1)因此红黑树的查询效率为O(logn)。
红黑树的插入
红黑树查询与BST没有区别,在此关注红黑树的插入与删除,另外注明本文只关注节点N或父节点P位于左子树的情况,右子树的情况可以根据对称得到。
关于插入,我们可以明确的一点是,应该插入节点应为红色,因为插入红色节点不会影响性质5,即黑高不变。
在此我们列出插入节点的若干种情况,以及对应的处理方法。
1 插入节点N为根节点
此时,我们只需要将插入节点进行染色,红→黑即可,如图所示
2 插入节点N的父节点P为黑色
此时我们不需要进行任何操作,因为插入红节点不影响性质5,且父节点P为黑色不影响性质4。
3 N的父节点P为红色,且叔叔节点U也为红色
如图所示
此时违反了性质4,我们进行的处理如下:
- 将P和U染为黑色
- 将祖父节点G染为红色
- 下一步关注组父节点G,进一步递归判断
之所以需要递归判断,是因为G的父节点可能为红色,再一次违反性质4。
4 父节点P为红色,叔叔节点U为黑色,P为左孩子,N为右孩子
如图所示
注意,这里所对应的还有P为G的右孩子,N为P的左子树的对称情况,在此不再赘述。
我们对这种情况需要进行的处理为N、P左旋,下一步关注P节点。
此时左旋后,我们的红黑树依然违反了性质4,因此进一步关注节点P,并且通过情况5进行处理
5 父节点P为红色,叔叔节点U为黑色,P为左孩子,N为左孩子
如图所示
注意,这里所对应的还有P为G的右孩子,N为P的右孩子的对称情况,在此不再赘述。
此时我们的处理方法如下:
- PG右旋
- P染黑
- G染红
通过以上步骤,我们发现,各个点位的黑高都没有发生变化,且去除了相邻的红色节点,因此完成了红黑树的调整。
红黑树插入总结
- 找到插入的位置,将红色新节点N插入
- 判断N是否是根节点,是的话,染为黑色,否则继续
- 判断N的父节点是否为黑色,是的话,返回,否则继续
- 判断N的叔叔节点是否为红色,是的话,将P、U染为黑,G染为红,N=G,进一步判断
- 判断N所在的分支是否和P所在的分支不一致,不一致的话,则进行旋转,上升N,下降P,并归为一边,继续判断
- 若P和N所在分支一致,则对P、G进行旋转,上升P,下降G,并交换P与G的颜色,返回
红黑树的删除
相比于插入,红黑树的删除也确实更加复杂。同样适用替换删除法,删除前驱或后继节点。
和AVL树一致,我们不关心最终删除的节点是否与想删除的一致,我们只关心被删除的节点,如果被删除的节点为红色,则较为简单,否则会比较复杂,我们将进一步的进行分析,同样地,以下分析依然基于N为左子树或P为左子树的情况,对称的情况不再赘述。
以下各节点的名字所代表的含义分别如下:
- X为最终被删除的节点
- N为替换X的节点
- S为X的兄弟节点,替换后也就是N的兄弟节点
- SL为S的左节点,SR为S的右节点
- P为N的父节点
- G为P的父节点,N的组父节点
1 删除节点X为红色
此时直接删除节点X,并用黑色的N节点(必然是黑色)替换,则不会影响性质4与性质5,是最为简单的一种删除情况。
2 替换节点N为根节点
替换后节点N为根节点,此时我们将节点N染色为黑色,相当于所有节点都去出了一个黑色节点,黑高一致,完毕。
3 S为黑,S右孩子为红,N为P的左孩子
如图所示,其中蓝色的P节点与SL节点,代表这些节点可红可黑。
之所以上来就考察如此复杂的情况,是因为这种情况可以通过一定操作完成删除的调整。同理,可以适用于N为右孩子,S左孩子为红的对称情况
具体操作如下:
- P、S左旋
- P与S交换颜色
- SR染色为黑
让我们对删除前后的子树进行分析。
- 我们假设, C ( x ) C(x) C(x)为判断节点x是否为黑色的函数,若为黑色,则 C ( x ) = 1 C(x)=1 C(x)=1,反之, C ( x ) = 0 C(x)=0 C(x)=0
- 删除前, b h ( G → N ) = G → P → X → N = 2 + C ( P ) bh(G→N)=G→P→X→N=2+C(P) bh(G→N)=G→P→X→N=2+C(P), b h ( G → S L ) = G → P → S → S L = 1 + C ( P ) + C ( S L ) bh(G→SL)=G→P→S→SL=1+C(P)+C(SL) bh(G→SL)=G→P→S→SL=1+C(P)+C(SL)且 b h ( G → S R ) = G → P → S → S R = 1 + C ( P ) bh(G→SR)=G→P→S→SR=1+C(P) bh(G→SR)=G→P→S→SR=1+C(P)
- 删除后, G → S L 与 G → S R G→SL与G→SR G→SL与G→SR不变, G → N G→N G→N改变为 1 + C ( P ) 1+C(P) 1+C(P)
- 旋转后, b h ( G → N ) = G → S → P → N = 2 + C ( P ) bh(G→N)=G→S→P→N=2+C(P) bh(G→N)=G→S→P→N=2+C(P), b h ( G → S L ) = G → S → P → S L = 1 + C ( P ) + C ( S L ) bh(G→SL)=G→S→P→SL=1+C(P)+C(SL) bh(G→SL)=G→S→P→SL=1+C(P)+C(SL)且 b h ( G → S R ) = G → S → S R = 1 + C ( P ) bh(G→SR)=G→S→SR=1+C(P) bh(G→SR)=G→S→SR=1+C(P),因为S和P的对调与颜色互换,使得经过SL的黑高不变的同时,N的黑高+1,恢复到了删除前。而通过对SR的染色,弥补了S染为 C ( P ) C(P) C(P)时的黑高减少,旋转前后根节点颜色不变,意味着不需要继续向上迭代。
- 旋转前后,针对之前的P和S来看,N侧的黑高+1
4 S为黑色,S左孩子为红,S右孩子为黑,N为P的做孩子
不再赘述对称情况,直接上图
操作如下:
- SL、S右旋
- SL染为黑色
- S染为红色
- 转入情况3处理
通过分析,旋转前后,不影响P到右子树任何叶节点的黑高。
5 S为红色,其余节点为黑色
如图所示
我们已经意识到在黑节点之中插入1个红节点,不会影响红黑树的定义,在这种情况下,我们面对S为红,其余为黑的操作如下:
- P、S左旋
- P染红
- S染黑
- 转入情况3或情况4
6 N、P、S、SL、SR全为黑
如图所示
我们需要做的是将S染红,将关注节点设为P。
相比于删除前,N和S的黑高同时下降1,P的左右子树保持了一致,即P满足红黑树定义,但经过P的路径,将比不经过P的路径黑高小1,这相当于P成为了替换节点,其父节点被删去的情况,因此根据P的父节点、兄弟节点进行进一步的判断,直到转入情况1或2或3。
7 P为红,N、S、SL、SR为黑
如图所示
此时我们交换P与S的颜色,S黑高减1,与N保持一致,相当于P的黑高减一,与情况6染色后情况一致,将关注节点设为P,进一步判断。
删除总结
分析了各种情况的删除,我们可以做一些简单的总结。
- 情况1、2、3,可以通过一定的处理,完成对红黑树的调整
- 情况4通过不改变黑高的方法,转换为了情况3
- 情况5通过不改变黑高的方法,转化为了情况3或4
- 情况6和7,通过改变另一侧的黑高,完成了P的子树调整,但过P和不过P产生了黑高的不一致,与P为替换节点的情况一致,需要对P进行进一步的判断
- 以上操作都建立在P或N为左子树情况,右子树情况进行对称即可。
总结
峣峣者易折,皎皎者易污。这句话用来形容AVL树、红黑树、最佳排序树实在再合适不过。
最佳的性质最容易破坏而难以保持,且“最佳”往往是一种全局性质,无法从局部进行把握和检查,一旦被破坏,也无法从局部调整恢复,这就是最佳排序树(这个的博客还没出)往往应用较少的原因。
而AVL树和红黑树则都是在检索效率和动态操作之间做出了取舍,纵观两者,其搜索效率性能都发生了常数级的退化,但同时也支持了局部的动态调整操作,两者的插入与删除均维持在O(logn)。而红黑树则是对检索效率的进一步放弃以及更加复杂的算法复杂度,换取了更少的旋转操作,无论是C++STL中的map,还是Java中的TreeMap底层都是红黑树的实现。
——2023.5.18