
大家好,很高兴又和大家见面啦!!! 在前面的内容中我们已经学习了两种树形查找结构:
为了避免 BST 的树高增长的过快而导致其查找效率降低,因此引入了能够将左右子树的高度差控制在 -1 \sim 1 之间的 AVL; AVL 通过判断树的 平衡因子,以此来决定是否需要进行平衡旋转:
正因如此,当我们进行频繁的插入或删除操作时,就很容易破坏 AVL 的平衡特性,而导致其需要进行频繁的平衡旋转操作; 为了优化这种情况,于是我们又再一次引入了一种新的自平衡排序树—— 红黑树。 那什么是 红黑树 呢?相比于 AVL 其又有哪些优势呢?从今天的内容开始,我们将会逐步揭开 红黑树 的神秘面纱; 下面就让我们一起进入今天的内容;
红黑树(Red-Black Tree, RBT)是一种满足如下 红黑性质 的 BST:
NULL 结点)都是黑色因此我们可以将其总结为十二个字:
黑高:一个结点的黑高是指从该结点出发(不含该结点)到达任一空叶结点的路径上黑结点总数 叶结点:在 RBT 中,“叶结点”通常指“失败结点”(又名:空叶结点/NULL结点/外部结点) 下面我们就来通过一棵红黑树来进一步说明这些概念:
flowchart TB
a[6<br>bh = 2]
b[2<br>bh = 1]
a--->b
b--->b1[NULL<br>bh = 0]
b--->b2[NULL<br>bh = 0]
c[15<br>bh = 2]
a--->c
d[11<br>bh = 1]
e[18<br>bh = 1]
c--->d
c--->e
f[8<br>bh = 1]
g[14<br>bh = 1]
d--->f
f--->f1[NULL<br>bh = 0]
f--->f2[NULL<br>bh = 0]
d--->g
g--->g1[NULL<br>bh = 0]
g--->g2[NULL<br>bh = 0]
e--->e1[NULL<br>bh = 0]
h[20<br>bh = 1]
e--->h
h--->h1[NULL<br>bh = 0]
h--->h2[NULL<br>bh = 0]
classDef Red color: white, fill: red, stroke: red, stroke-width:2px
classDef Black color:white, fill: black, stroke:black, stroke-width:2px
class a Black
class b Black
class b1 Black
class b2 Black
class c Red
class d Black
class e Black
class f Red
class g Red
class e1 Black
class h Red
class f1 Black
class f2 Black
class g1 Black
class g2 Black
class h1 Black
class h2 Black在上图中的这棵 RBT 中,其 根结点 与 叶结点 一定是黑色,并且这些 叶结点 一定是 NULL 结点; 对于任一结点,其黑高指的是从该节点开始,到任一 叶结点 的路径上,不包含该结点的黑结点数量:
6 ,其到任一 叶结点 的路径上都会包含2个黑结点,因此其黑高为 2: 6 ---> 2 ---> NULL ,包含除结点 6 外的两个黑结点:2 和 NULL6 ---> 15 ---> 11 ---> 8 ---> NULL ,包含除了结点 6 外的两个黑结点:11 和 NULL2 ,其到任一 叶结点 的路径上都会包含1个黑结点,因此其黑高为 1 : 2 ---> NULL ,包含除结点 2 外的一个黑结点:NULL对于结点 6 而言,其左子树之所以为 黑 ,是因为其右子树 15 的黑高为 2 ,为了满足 黑路同 ,因此结点 2 只能为 黑 ; 若我们将 2 变为红色,则会导致从结点 6 出发,到左子树中的任一叶结点,与到右子树中的任一叶结点所包含的黑结点数不一致;
flowchart TB
a[6<br>bh_l = 1<br>bh_r = 2<br>bh_l != bh_r]
b[2<br>bh = 1]
a--->b
b--->b1[NULL<br>bh = 0]
b--->b2[NULL<br>bh = 0]
c[15<br>bh = 2]
a--->c
d[11<br>bh = 1]
e[18<br>bh = 1]
c--->d
c--->e
f[8<br>bh = 1]
g[14<br>bh = 1]
d--->f
f--->f1[NULL<br>bh = 0]
f--->f2[NULL<br>bh = 0]
d--->g
g--->g1[NULL<br>bh = 0]
g--->g2[NULL<br>bh = 0]
e--->e1[NULL<br>bh = 0]
h[20<br>bh = 1]
e--->h
h--->h1[NULL<br>bh = 0]
h--->h2[NULL<br>bh = 0]
classDef Red color: white, fill: red, stroke: red, stroke-width:2px
classDef Black color:white, fill: black, stroke:black, stroke-width:2px
class a Black
class b Red
class b1 Black
class b2 Black
class c Red
class d Black
class e Black
class f Red
class g Red
class e1 Black
class h Red
class f1 Black
class f2 Black
class g1 Black
class g2 Black
class h1 Black
class h2 Black因此结点 2 只能为 黑;
RBT 总共有3条性质:
这里我们先介绍前两条性质;
当从根结点出发,到任一叶结点的简单路径最短时,这条路径上的结点必然都是黑色,如最短简单路径:6 --> 2 ---> NULL 当某条路径最长时,这条路径必然是由黑结点与红结点相间构成,如:6 ---> 15 ---> 11 ---> 14 ---> NULL 可以看到这两条路径的长度分别为:3 和 5 ,而
6 ,那么根据红黑性质——不红红 与 根叶黑 ,对应的路径上其结点的颜色一定是:黑 ---> 红 ---> 黑 ---> 红 ---> 黑 ---> 黑 ,其根结点的黑高为: bh = 3 ;3 ,并且该路径上的结点均为黑色,即:黑 ---> 黑 ---> 黑,其根结点的黑高为:bh = 2这里有朋友可能会好奇,为什么这里的性质说的是 不大于 而我们得到的结论是 一定小于? 对于这个问题,我给出的解释是:我这里给出的实例所计算路径长度的方法与性质1的表述是不一致的:
前面我所展示的路径长度计算方法使用的是方法一,因此我们会得出 严格小于 这个结论; 当我们采用方法二进行计算时,那么其最短路径长度与最长路径长度分为为:
6 ---> 2 ,长度为 26 ---> 15 ---> 11 ---> 14 ,长度为 4可以看到此时的最长路径长度就与最短路径长度的两倍相等; 因此 性质1 的表述是以方法二的方式计算路径长度得出的结论,而 严格小于 也属于 不大于 的范畴,所以,我们如果通过方法一的方式计算路径长度,我们同样也可以说 不大于; 希望大家能够准确辨别空叶结点 与 内部叶结点 这二者的区别,切记不能将二者混为一谈!!!
性质1的验证,我们还有一个更简便的方法——插入法; 这里我们以下图进行简单的说明:
flowchart LR
a[黑]
b[黑]
c[黑]
a--->b--->c
classDef Red color: white, fill: red, stroke: red, stroke-width:2px
classDef Black color:white, fill: black, stroke:black, stroke-width:2px
class a Black
class b Black
class c Black在这条仅由黑色块组成的路径中,我们需要插入红色块,并且保证该路径的起点与终点为黑色,且插入的红色块不能连续,那么我们能够插入的红色块最多只有2块:
flowchart LR
a[黑]
b[黑]
c[黑]
a--->d[红]--->b--->e[红]--->c
classDef Red color: white, fill: red, stroke: red, stroke-width:2px
classDef Black color:white, fill: black, stroke:black, stroke-width:2px
class a Black
class b Black
class c Black
class e Red
class d Red当我们去掉终点的黑色块后,我们不难发现,这时的红色块与黑色块的数量是相同的:
flowchart LR
a[黑]
b[黑]
a--->d[红]--->b--->e[红]
classDef Red color: white, fill: red, stroke: red, stroke-width:2px
classDef Black color:white, fill: black, stroke:black, stroke-width:2px
class a Black
class b Black
class e Red
class d Red每个黑色块后都紧跟一个红色块,这样我们就能得到以下结论:
1因此对于最短路径长度为 N 的 RBT ,其最长路径长度为:
这也进一步验证了性质1:
由性质1可知,在红黑树中,其最长路径所包含的红结点是最多的,且不管是用方法一计算长度还是方法二计算长度,其红结点的数量一定不会超过黑结点的数量,即对于长度为 2N 的最长路径:
N - 1 ,黑结点的数量一定是 N + 1N,黑结点的数量一定是 N当我们只分析 内部结点 时,即采取 方法二 的方式计算一条路径的长度,那么对于 n 个内部结点的红黑树,其树高为 h ,这也就表明该 RBT 的最长路径长度为 h ;
对于一棵 RBT 而言,当其最长路径中的红结点数量最多时,其黑高最小;
在这棵 RBT 中,其仅计算内部结点的最长路径长度为 h ,这样我们就能得到红结点最多时,其红黑结点的数量均为 \frac{h}{2} ; 而我们要计算根结点的黑高时,我们需要去掉根节点,并加上路径的终点——空叶结点,这也就是说,根结点的黑高至少为 \frac{h}{2} ; 由二叉树的性质我们可知,对于结点数量为 n ,树高为 h 的二叉树,其结点数量 n 与树高 h 之间的关系为:
在 RBT 中,其内部结点数 n 与其黑高 bh 之间的关系为:n \geq 2^{bh} - 1 ,该结论我们可以通过 数学归纳法 进行证明;
由 分治思想 我们可以得到,RBT 的内部总结点数是由三部分组成:
因此我们要求 RBT 的内部结点总数 n 就需要获取其左右子树中的结点总数; 假设对于一棵 RBT ,其任一结点 x 的黑高为 bh_x ,以该结点为根的子树的内部结点总数为 n_x ; 当该结点为 空叶结点 时 ,其黑高 bh_{x} = 0 ,其内部结点数为:n_{x} = 2^{bh_{x}} - 1 = 2^0 - 1 = 1 - 1 = 0,此时结论成立; 在 RBT 中,其任一结点 x 的左右子树的黑高至少为:bh_{x} - 1 这是因为当结点 x 的子树根结点为黑结点时,其子树的黑高为:bh_{x}- 1 ;
flowchart TB
a[bh = 2] ---> b[bh = 1]
a--->c[bh = 1]
b--->b1[NULL<br>bh = 0]
b--->b2[NULL<br>bh = 0]
c--->c1[NULL<br>bh = 0]
c--->c2[NULL<br>bh = 0]
classDef Red color: white, fill: red, stroke: red, stroke-width:2px
classDef Black color:white, fill: black, stroke:black, stroke-width:2px
class a Black
class b Black
class c Black
class b1 Black
class c1 Black
class b2 Black
class c2 Black当结点 x 的子树根结点为红结点时,其子树的黑高为:bh_{x};
flowchart TB
a[bh = 1] ---> b[bh = 1]
a--->c[bh = 1]
b--->b1[NULL<br>bh = 0]
b--->b2[NULL<br>bh = 0]
c--->c1[NULL<br>bh = 0]
c--->c2[NULL<br>bh = 0]
classDef Red color: white, fill: red, stroke: red, stroke-width:2px
classDef Black color:white, fill: black, stroke:black, stroke-width:2px
class a Black
class b Red
class c Red
class b1 Black
class c1 Black
class b2 Black
class c2 Black因此我们可以假设:任一结点 x 其左右子树的内部结点数量为:
$$ n_{x_l} \geq 2^{bh_x - 1} - 1 \ n_{x_r} \geq 2^{bh_x - 1} - 1 $$
那么对于以结点 x 为根的子树,其内部结点数量 n_x 为:
$$ \begin{align*} n_x &= n_{x_l} + n_{x_r} + 1 \ n_x &\geq 2^{bh_x - 1} - 1 + 2^{bh_x - 1} - 1 + 1 \ n_x &\geq 2 * 2^{bh_x - 1} - 1 \ n_x &\geq 2^{bh_x} - 1 \end{align*} $$
在前面我们已经证明了,一棵高度为 h 的 RBT ,其黑高 bh \geq \frac{h}{2} ; 现在我们只需要将黑高 bh 带入到内部结点 n 与 黑高 bh 的公式 n \geq 2^{bh} - 1 中,就可以得到性质2:
$$ \begin{align*} n &\geq 2^{bh} - 1 \ n &\geq 2^{\frac{h}{2}} - 1 \ n + 1 &\geq 2^{\frac{h}{2}} \ \log_2 (n + 1) &\geq \frac{h}{2} \ 2 * \log_2 (n + 1) &\geq h \end{align*} $$
当一棵 RBT 的黑高为 bh = h 时,其对应的树高为与内部结点数分别为:
可见,RBT 的 适度平衡 ,由 AVL 的 高度平衡 ,降低到了 任一结点左右子树的高度,相差不超过 2 倍。这种调整同时也降低了动态操作时的调整频率。
对于一棵动态查找树,若插入和删除操作比较少,查找操作比较多,则采用 AVL 比较合适,否则采用 RBT 比较合适。 由于维护 AVL 这种 高度平衡 所付出的代价比获得的效益大的多,因此在实际应用中,RBT 的应用更为广泛,如 C++ 中的 map/set 、JAVA 中的 TreeMap/TreeSet 就是用 RBT 实现的。
通过本文的学习,我们系统性地探讨了红黑树(Red-Black Tree, RBT)的核心定义与关键性质。 红黑树作为一种自平衡的二叉查找树,通过其独特的五大性质确保了树的近似平衡:
与AVL树对高度平衡的严格要求不同,红黑树以"适度平衡"换取了在频繁插入删除场景下更少的旋转操作,这使得它的实际应用更为广泛,如C++ STL中的map/set、Java的TreeMap/TreeSet 📌 核心知识点回顾
🔜 下一篇内容预告 在接下来的篇章中,我们将深入探讨红黑树最核心的操作——插入操作,重点分析:
掌握这些情况及其处理流程,是真正理解和应用红黑树的关键。敬请期待!
互动与分享
感谢您的耐心阅读! 关注博主,不错过更多技术干货。我们下一篇再见!