序言
对红黑树有所了解的人都知道,红黑树是一种比较复杂的数据结构。,插入操作要分5种不同的情况来处理,而删除则有6种情况(不同教程的说法可能略有出入)。但是你有没有想过,红黑树为什么要将节点区分成红色和黑色两种?为什么红黑树的维护会这么复杂?
2-3-4树
讲红黑树之前,我们需要了解一下2-3-4树,因为红黑树实际上就是用二叉树去模拟 2-3-4树。
我们知道,普通二叉查找树之所以会出现查找效率退化的情况,是因为不平衡,极端的一种情况是每个内部节点只有一个子节点,如下图。这时候二叉查找树已经变成了链表。
而另一个极端则是,所有内部节点都有两个非空子节点,且所有叶节点的深度都相等,被称为“满二叉查找树”,这个时候查询效率是最高的。
2-3-4树是这样一种树:
- 2-3-4树中每一个元素都大于或等于它左子树中的任意元素,这是查找树的基本特性
- 2-3-4树中每个内部节点可以包含1个、2个或3个的元素,他们一定有2、3或4个子节点,而满二叉查找树每个内部节点仅含1个元素,有且只有两个子节点
- 2-3-4树中所有叶节点的深度都相等,这一点跟满二叉查找树一样
2-3-4树就是满二叉查找树的泛化,满二叉查找树是2-3-4树的特殊情况。
由于插入、删除实现起来太过复杂,2-3-4树在实际应用中极为少用,但是红黑树却来源于2-3-4树,它使用标上颜色的二叉查找树来模拟2-3-4树。实际上,每一棵红黑树也都是一棵2-3-4树,它们是等价的。
红黑树
如果我们把2-3-4树稍微转换一下,它就成了红黑树。
对于2-3-4树中每一个节点,它可能有三种不同的情况:
如果它只有一个元素,则不用修改它,直接让它作为红黑树的黑色节点。
如果它有两个元素,三个子节点(分别为左、中、右子节点),那么其中较小的元素成为红黑树的黑色节点,较大的元素成为红色节点并作为黑色节点的右子节点,而红色节点的两个子节点为2-3-4树原节点的中、右子节点。
如果它有三个元素,四个子节点(1,2,3,4子节点),那么第二大的元素成为红黑树的黑色节点,而最小的元素和最大的元素各自成为两个红色节点,作为黑色节点的左、右子节点。原节点中的1,2子节点成为左红色节点的子节点,3,4子节点成为右红色节点的子节点。
上面的变换,把2-3-4树的单个节点变成一棵小的红黑树,这棵小红黑树以黑色节点为根节点,且只包含一个黑色节点。
对2-3-4树的所有节点进行这种变换,就得到了这棵2-3-4树对应的红黑树。
根据2-3-4树的性质,我们可以得到红黑树的一些性质:
- 根节点是黑色的
- 红色节点的子节点一定是黑色的,也就是一条路径上不存在连续两个或以上的红色节点
- 从根节点出发,到任意叶节点的路径上,黑色节点的数量都是相同的
- 最深的叶节点深度不超过最浅的两倍
解释一下第四条性质。
因为从根节点到叶节点任意路径上黑色节点的数量是相同的,所以最长路径只会比最短路径多出一些红色节点。又因为不存在连续的红色节点,所以极端情况下最长路径只能是红黑相间的形式,同时最短路径则只包含黑色节点。
在这种情况下最长路径的红色节点跟黑色节点的数量相同,而最短路径只有黑色节点,所以最长路径长度会是最短的两倍。当然这是最极端的情况,绝大多数情况下是小于这个差距的。
正是这一条性质的约束,使得红黑树成为平衡的二叉查找树。
对比
普通二叉查找树
跟完全没有约束的普通二叉查找树相比,红黑树要保证约束始终成立,在插入和删除的时候就必须进行额外的维护操作。这也是红黑树比普通二叉查找树复杂得多的原因。
满二叉查找树
满二叉查找树,它的每一条根节点到叶节点的路径长度都是相等的,所以查询效率最高。但是在经历大量插入删除后还要保持满二叉树(多数情况只能是完全二叉树)的状态,这种约束太过严格,需要大量的维护操作,并不现实。
红黑树放宽了这种约束,只要求最长的路径长度不超过最短的两倍,在修改和查询效率之间做了平衡。
AVL树
AVL树的平衡约束是,树中任何节点的两个子树的高度最大差别为1。这是比红黑树更严格的约束,因此AVL树在修改时需要进行更多的维护,理论上查询效率也会比红黑树更高。但是工程上这个查询效率的优势并不明显,大多数应用还是采用红黑树而不是AVL树。