红黑树
红黑树 和AVL 树一样,都是平衡二叉搜索树。
但是 AVL 树 比红黑树更加严格,AVL 树要求左右子树的高度差 <= 1 ,而红黑树只能保证最长路径不超过最短路径的 2 倍。
AVL 树通过 旋转 控制平衡,为了达到它严格的规定,需要旋转的次数较多。红黑树通过旋转+ 变色 控制平衡,同时也因为规定比较宽松,所以旋转的次数少。
在插入方面,显然是红黑树更具有效率优势。
在查找方面,由于 AVL树 比 红黑树 更平衡一些,所以效率更快,但它们的效率都是 O (log n)级别的,所以红黑树瑕不掩瑜,多数情况下使用的都比比AVL树更多。
红黑树的规则
上面提到:红黑树能保证最长路径不超过最短路径的 2 倍。它是如何做到的呢?这和红黑树的构造规则相关,如下:
1. 每个节点不是红色 就是黑色。
2. 根节点是黑色的。
3. 如果一个节点是红色的,它的两个孩子必须是黑色 (即不能连续出现两次红色节点)。
4. 对于任意一个节点,从该节点到其所有NULL节点的简单路径上,均包含相同数量的黑色节点。
这四条规则共同约束了树的结构,最终保证了红黑树的核心性质:从任意节点出发,到其所有叶子节点的最长路径,不会超过最短路径的 2 倍。
举一些例子:


仔细观察,你会发现这些树都满足了红黑树的规则,同时,它们也做到了最长路径 不超过最短路径 的 2 倍。
红黑树的调整
AVL树于1962年诞生,红黑树比它晚了 10 年,所谓前人栽树,后人乘凉,红黑树 的旋转调整逻辑和 AVL树 完全一致,AVL树在上一篇博客讲过,那么我们接下来所需要讨论就是红黑树的专属调整逻辑:变色。
在讲调整逻辑之前,我们得先搞清楚:什么情况下需要调整。
从第一个规则可知,新插入的节点非黑即红,我们来分类讨论:
黑 :当插入节点为黑色时,我们会违法第四个规则,因为插入一个黑色节点,无论如何都不能保持每条路径上的黑色节点相同了(注意:走到空节点才算一条路径),所以绝对不能插入黑色节点 。
红:既然不能插入黑色,那么插入的必然就是红色节点了。但是,插入红色节点的情况有两种,通过父节点来区分。父节点为黑 ,没有违法任何规则,即满足红黑树,故无需调整。父节点为红,违反第三个规则,此时我们就需要进行调整了。
那么,如何调整呢?先看图:

上图是一颗新插入了节点的红黑树,为了方便说明,我们对调整所需的相关节点进行标记:新节点为 c(意为:current,表示当前节点),新节点的父节点为 p (意为:parent,表示父节点),父节点的兄弟为 u(意为:uncle,表示 "叔叔" 节点),父节点的父节点为 g(意为:grandparent,意为 "爷爷" 节点)。

对红黑树的调整,最关键的就是 "叔叔" 节点。
在上面这颗树中, p 和 u 存在且为红色,g 为黑色,此时,我们将 p 和 u 变为黑色,g 变为红色,完美解决啦!

但,当 u 不存在或者u 为黑色的时候,单纯的变色是无法解决问题的,比如:
u 不存在时:

此时,树的结构违反第四个规则。从根节点到 g ,再到 g 的右孩子这条路径比其他路径少一个黑节点。那么,怎么办?答案是,旋转!只需要对 g 为根节点的树进行一次右单旋即可解决问题。

(当 c 节点插入在 p 的不同位置时,使用的旋转方法不同)
u 为黑色时:
u 是黑色节点的情况是什么样的?按照之前的逻辑,既然我们需要调整了,那么 c 与 p 必然是红色节点,g 必然是黑色节点,如果 u 是黑色,原来的树不是违反规则了吗?我们难道能给一颗违反规则的树插入节点吗?
我们当然不能给一颗违反规则的树插入节点,但是,仔细观察就会发现,当我们调整时,可能会对 g 进行变色,本来是黑色的 g 可能会变成 红色。
假如 g 的父节点也是红色,那么,我们的调整并没有将树调整好,需要继续向上进行,此时我们将 g 设置为 c (当前节点),就有可能出现 u 为黑色的情况。

对于这样一颗树,我们要做的是 变色加旋转 。

经过 变色加旋转 处理的,不需要继续向上调整。
这里给出插入的核心代码:


因为代码量比较大,这里只贴了核心代码的一部分,另一部分逻辑即可。