1、什么是红黑树
红黑树是一种自平衡的二叉查找树。对于刚学习的人了解清楚这句话就够了,重点在于为什么有红色和黑色和怎么做到自平衡。(当然,在你学习红黑树之前你肯定要知道什么是二叉查找树。
红黑树需要满足以下五个原则
- 每个节点不是红色就是黑色的;
- 根节点是黑色的;
- 每一个叶子节点(NULL节点)都是黑色的;
- 红色节点的孩子节点一定是黑色的(红色节点的父母节点也一定是黑色的);
- 每个节点到子孙节点的所有路径上包含相同数量的黑色节点(俗称为黑高相同)。
2、为什么是红和黑?
在没有深入研究之前,大家都是否会跟我一样有一个疑虑?那就是为什么是红色和黑色两个颜色?当然不说是颜色为啥是红和黑,因为很容易可以想到红黑树应该也能被叫成蓝绿树。更清晰一点的问题是:为什么是两个颜色呢?
先停下来,简单设想一个场景,往红黑树中插入一个节点:
为了保证插入后红黑树的平衡,我们需要让插入的节点是红色还是黑色呢?
我们先来假设新节点为黑色,在插入之后,我们就一定会违反原则5。因为本身树是平衡的,所以经过新节点的路径的黑色节点数就肯定比其他不经过新节点的黑色节点多一个,从而一定需要调整。
而当新节点为红色,在插入之后,我们需要考虑的情况是,新节点的父母节点是否为红色。假设是黑色,则皆大欢喜,不需要任何调整;如果是红色,则违反了原则4,需要进行后续的调整。
综上所得,我们如果让新插入的节点为红色的时候,可以避免一些需要插入后调整的情况。言至于此,是否对一开始的问题有一点头绪?
我们来考虑红黑树的黑色高度(每个节点到子孙节点的所有路径上的黑色节点)。由于红黑树的规则,红色节点不能连续,因此在最坏情况下,红黑树的黑色高度至多是红色高度的两倍。由于红色节点最多占一半的高度,所以总的高度至多是黑色高度的两倍。所以,并不是单纯的两个颜色就可以起作用,两种颜色其实也只是五条原则中的一条,实际应该这五条原则结合起来,可以使树保持相对的平衡和树的高度不会过高,从而限制了搜索的最坏情况时间复杂度。
3、为什么需要红黑树?
我们先回顾一下AVL树,普通的二叉搜索树在一些特定的插入和删除操作序列中可能会导致树的不平衡,使得查找操作的性能下降到O(n)的级别。AVL树引入了平衡条件,通过保持树的平衡,确保树的高度保持在较小范围内,从而保证了查找、插入和删除操作的最坏情况时间复杂度为O(log n)。
那我们来看AVL树有什么问题,AVL树要求任意节点的左右子树的高度差不超过1。这使得AVL树在插入和删除操作时需要更频繁的旋转,可能导致性能开销较大。而红黑树的用于维系平衡的五个原则更为宽松,使得红黑树可以通过染色来保持树的相对平衡,允许树的高度相对较大,减少了维护平衡所需的旋转操作。
可以简单想象一个一直插入数据的场景,假设新数据刚好都落在A节点的左子树和右子树上,如果新数据持续得落在A节点的右子树上,AVL树为了维系起本身的平衡,需要不断的旋转,而红黑树可以通过染色来避免一些旋转操作。当然我们明确一个原则,旋转操作是比染色操作消耗更大。但是,这并不是说红黑树在所有情况下都比 AVL 树插入更高效。AVL 树在某些场景中可能表现得更好,尤其是在对搜索性能有更高要求的场景。选择使用哪种树结构应该根据具体应用的需求来决定。
4、补充
这是一棵红黑树,每个结点都是红色或者黑色,红色结点的两个子节点都是黑色,且每个结点到其后代叶节点的每条简单路径上,都包含相同数目的黑色结点。同时可以看到每个名字为NULLLEAF的结点都是叶子结点,且都是黑色的。
为了便于处理代码种的边界条件,使用一个哨兵(黑色,NIL)来代表所有的叶子结点(黑色,NULLLEAF)。哨兵结点是一个跟树中普通结点有相同属性的结点。它的颜色为黑,而它的父结点,左孩子结点,右孩子结点和键值可以被设置为任意值,所有指向叶子结点的指针都会指向哨兵结点。设置根节点的父结点也为这个哨兵结点。
同样的一颗红黑树,省略哨兵结点。
详细代码放在:Meta11ic0/MyEasyRBTree: RBTree base on Introduction to Algorithms/红黑树基于自己研究《算法导论》 (github.com) 个人博客为:HALIWUWU