红黑树是一个比较复杂的数据结构,相信不少人也只知其名而不知其意,由于理解它的原理确实须要花费必定的功夫。之因此写这篇文章,也是为了更好的理解 Java 中 TreeMap 的源码。html
写以前,搜了下网上的文章,说实话,看完有点懵,大部分一上来就给你它的五大性质,而后就是一顿插入、删除、旋转操做,就完事了,理解起来至关吃力。node
本文将结合 2-3-4 树,按部就班地介绍红黑树的由来和原理,相信看完以后,你对它会有更清晰的认识。此外,这里描述的是普通红黑树,而不是它的变体左倾红黑树(LLRB),这一点须要注意。算法
红黑树的由来要从二叉查找树提及。二叉查找树是一颗二叉树,它每一个结点的值都大于其左子树的任意结点而小于右子树的任意结点,它结合了链表插入的灵活性和有序数组查找的高效性(二分查找)。编程
对于使用二叉查找树的算法,它的运行时间取决于树的形状,而树的形状又取决于结点插入的前后顺序。如上图所示,最好状况下,N 个结点的树是彻底平衡的,每条空连接到根结点的距离都为 ~lgN;而在最坏的状况下,搜索路径上可能有 N 个结点,退化成了链表。数组
因此,为了保证运行时间始终在对数级别,在动态构建二叉查找树时,但愿保持其平衡性,也就是下降树的高度,使其尽量为 ~lgN,这样就能保证全部的查找都能在 ~lgN 次比较内结束,就像二分查找那样,这样的树被称为平衡二叉查找树。数据结构
第一个自平衡二叉查找树就是AVL 树,它规定,每一个结点的左右子树的高度之差不超过 1。在插入或删除结点,打破平衡后,就会经过一次或屡次树旋转来从新平衡。编程语言
AVL 树是严格平衡的,适用于查找密集型应用程序,由于在频繁插入或删除结点的场景下,它花费在树旋转的代价过高。源码分析
而红黑树就是一种折中方案,它不追求完美平衡,只求部分达到平衡,从而下降在调整时树旋转次数。网站
说到红黑树,就不得不提 2-3-4 树,由于,红黑树能够说就是它的一种特殊实现,对它有所了解,很是有助于理解红黑树。3d
保持平衡,无非是为了下降树的高度,若是把二叉查找树通常化,容许一个结点保存多个值,变成多叉树,也可认为是下降了高度。
确切地说,标准二叉查找树中的结点称为2-结点(一个值两个子结点),如今引入3-结点(两个值三个子结点)和4-结点(三个值四个子结点),这样就能获得一颗 2-3-4 树(也称为 2-4 树)。
2-3-4 树是 4 阶 B 树,全部数据按排序顺序保存,全部叶子结点都在相同的深度。对于大多数编程语言,直接实现 2-3-4 树比较困难,而红黑树的实现相对要简单容易,这也是红黑树应用普遍的一部分缘由。
红黑树是二叉树,全部的结点都是2-结点,因此为了可以表示3-结点和4-结点,为结点引入了颜色属性:
如上图所示,若是把红黑树的红色结点和其父结点放平,它的结构就和左边的 2-3-4 树同样。
如今,来看下红黑树的性质:
这些性质没必要去背,就算记住后也绝对会忘,应该结合着 2-3-4 树理解性记忆。
另外,红黑树中的旋转和颜色翻转,就至关于 2-3-4 树中的拆分和合并,而且 2-3-4 树结点的拆分和合并,理解起来至关简单。对比分析和理解红黑树的操做,绝对让你眼前一亮。
在分析插入和删除以前,先了解下什么是树旋转。树旋转是二叉树中调整子树的一种操做,经常使用于调整树的局部平衡性,它包含两种方式,左旋转和右旋转。
其实旋转操做很容易理解:左旋转就是将用两个结点中的较小者做为根结点变为将较大者做为根结点,右旋转恰好于此相反,如上图所示:
红黑树的旋转其实就是为了确保和其结构相同的 2-3-4 树的一一对应关系,同时保证红黑树的有序性和平衡性。
接下来,就结合 2-4 树分析结点的插入,首先 2-4 树的插入逻辑是这样的:
2-4 树插入的都是叶子结点,红黑树插入的结点都是红色的,由于在 2-4 树中,待插入结点都认为能够插入到一个多值结点中。
这里假设待插入结点为 N,P 是 N 的父结点,G 是 N 的祖父结点,U 是 N 的叔叔结点(即父结点的兄弟结点),那么红黑树有如下几种插入状况:
这三种状况比较简单,就放在一块儿说明了,它们都不涉及旋转,只涉及颜色翻转,换句话说就是只是结点合并无拆分。
状况 1 和 2,不影响红黑树的性质,不会打破平衡,直接插入便可:
状况 3,P 为红色(不是根结点),U 也是红色,两个树插入状况以下:
以 [7, 5, 9, 3] 输入序列为例,两个树构建过程以下:
状况 4,P 为红色,而 U 为黑色,此时,在 2-4 树看来这个结点就是一个 3-结点,直接插入变成 4-结点;而对于红黑树,它为了和这个 2-4 树结构保持一致,会根据不一样的状况作旋转,分别有如下四种可能:
相反的:
以 [7, 5, 9, 3, 4] 输入序列为例,也就是在上图的基础上,插入 4,演示 P 为左,N 为右,树的旋转过程:
其余状况,左右互换便可,可自行尝试分析。这里给出最开始提供的红黑树和 2-3-4 树它们的动态构建过程,输入序列为 [7, 5, 9, 3, 4, 8, 10, 11, 12, 13, 14, 15],首先是 2-3-4 树的构建:
红黑树构建时会有一次根结点调整,可注意一下:
二叉查找树的结点无非是有两个子结点,有一个子结点和叶子结点三种,其中有两个子结点的 M 结点的删除逻辑是:
因此,删除任一结点的问题就简化成了:删除一个最多只有一个孩子的结点的状况,而且全部的删除操做都在叶子结点完成,只不过删除的结点再也不是一开始想删除的结点,但结点的值最终是删除了,而树结构的变化与简化问题相比,并不重要。
在分析红黑树的删除以前,简单来看下 2-3-4 树的删除状况。
它相似二叉查找树的删除,实际的删除操做也是在叶子结点完成,只不过在删除的过程当中涉及到结点的合并,主要有 3 种不一样的状况:
上面这些状况,有一个前提就是,在遍历查找待删除结点时,必须保证路过的结点都至少有 2 个 key,不是的话就须要合并结点。这点比较难理解,在插入时,会把遍历过程当中遇到的4-结点 进行拆分,相对的,在删除时,就要保证遍历的结点至少有 2 个 key,也就至关于把以前拆分的进行了合并。
如下图示演示了上述的每种可能的删除状况:
简单来讲,理解 2-3-4 树删除的重点就是:
红黑树的删除也一样相似二叉查找树,不过要考虑平衡,也就是结点颜色问题,要麻烦一点。
首先声明一点,接下来讲的红黑树叶子结点和二叉查找树叶子结点相同,若是要强调红黑树结点是空的叶子结点 NIL 会特殊说明,画图会使用黑色方框表示。
假设待删除结点为 M,若是有非叶子结点,称为 C,那么有两种比较简单的删除状况:
注意:M 有且仅有一个非叶子的左或右孩子结点,至关于 2-3-4 树删除的状况 1。
这两个状况,本质都是删除了一个红色结点,不影响总体平衡。以 [7, 5, 9, 3, 4] 输入序列构建的红黑树为例,演示以上两种比较简单的状况:
删除比较复杂的是 M 和 C 都是黑色的状况,此时 M 确定是叶子节点,而 C 确定是 NIL 结点,若是不是这样的状况将违反性质5。
一个黑色结点被删除会打破平衡,须要找一个结点填补这个空缺,假设待删除结点为 M,删除后它的位置上就变成了 NIL 结点,为了方便描述,这个结点记为 N,P 表示 N 的父结点,S 表示 N 兄弟结点,S 若是存在左右孩子,分别使用 SL 和 SR 表示,那么删除就有如下几种状况:
删除后,N 变成了根结点,也就是说删除前只有 M 这一个结点,直接删除便可。
S 是红色,那么它必有两个孩子结点,且都为黑色,并且 P 也确定是黑色。此时,交换 P 和 S 的颜色,而后对 P 左旋转,以下:
如今,结点 N 的父结点变成了红色,兄弟结点变成了 SL,此时就能够按照状况 四、五、6继续处理。
P 是黑色,S 也是黑色,而且 S 也没有非空的孩子结点。此时,直接将 S 变成红色,那么通过 S 的路径也就少了一个黑色结点,总体上就致使通过 P 的路径比原来少了一个黑色结点,把不平衡状态从结点 N 转移到告终点 P,能够把 P 按 状况1 处理,直到遇到根结点,以此造成递归:
P 是红色,S 是黑色,而且 S 也没有非空的孩子结点。此时,只要交换 P 和 S 的颜色,正好填补了少一个黑色结点的空缺,也就是恢复了平衡的状态:
P 任意颜色,S 黑色,S 的左孩子红色,(S 有右孩子也是红色)。此时,对 S 右旋转,并交换 S 和 SL 的颜色:
其实就是把这种状况,转成了 状况 6 进行处理。
P 任意颜色,S 黑色,S 的右孩子红色,(S 有左孩子也是红色)。此时,对 P 左旋转,交换 P 和 S 的颜色,并将 SR 变成黑色:
此时恢复平衡的状态,不管 P 以前是什么颜色,N 都比以前多了一个黑色父结点。假设 P 原先是红色的,如今变成了黑色;假设原先是黑色的,如今 P 又多了一个黑色的父结点 S,因此,不管怎样,通过结点 N 路径增长了一个黑色结点。
以上 6 种状况,结点 N 都是左孩子,若是是右孩子,只需把左右对调便可。类比 2-3-4 树的删除,理解黑色结点删除后的关键就是:
最后来看下 2-3-4 树和红黑树动态删除的过程,输入序列为 [7, 5, 9, 3, 4, 8, 10, 11, 12, 13, 14, 15, 16],删除顺序是 [16, 13, 11, 7, 15, 14, 8, 4, 9, 10, 5, 3, 12],首先是 2-3-4 树的动态删除过程:
红黑树动态删除的过程:
红黑树确实比较复杂,单纯的分析性质和旋转,意义不大,而 2-3-4 树就不同了,它的插入和删除简单多了,而红黑树的旋转和变色最终也是为了和同构的 2-3-4 树保持一致,本文就是相互结合分析,互相印证,相信会相对容易理解一点。
动图来自网站:www.cs.usfca.edu/~galles/vis… 它支持单步调试,有兴趣能够试一下。
搜索公众号「顿悟源码」获取更多源码分析和造的轮子。