红黑树
介绍
红黑树是一种自平衡的二叉查找树,是一种高效的查找树。它是由 Rudolf Bayer 于1972年发明,在当时被称为对称二叉 B 树(symmetric binary B-trees)。后来,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改为如今的红黑树。红黑树具有良好的效率,它可在 O(logN) 时间内完成查找、增加、删除等操作。因此,红黑树在业界应用很广泛,比如 Java 中的 TreeMap,JDK 1.8 中的 HashMap、C++ STL 中的 map 均是基于红黑树结构实现的。考虑到红黑树是一种被广泛应用的数据结构,所以我们很有必要去弄懂它。
红黑树的定义如下:
- 节点是红色或黑色
- 根节点是黑色
- 所有叶子都是黑色( NIL 节点)
- 每个红色节点的两个子节点都是黑色(从每个叶子到根的所有路径上不能有两个连续的红色节点)
- 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点(定义为黑高)
注意叶子节点 (NIL 节点不包含数据而只充当树在此结束的标志)。事实上,只要对红黑树的定义稍作修改,就能做到与 2-3 树一一对应,《算法》第四版中讲解的红黑树事实上就是与 2-3 树一一对应的,它规定红节点只能出现在左孩子的位置上。但是本文所讨论的红黑树还是和 2-3 树不同。
红黑树操作
旋转
左旋是将某个节点旋转为其右孩子的左孩子,而右旋是将某个节点旋转为其左孩子的右孩子。需要注意的是,对于红树的旋转,需要交换旋转的两个节点的颜色,下图就是交换 C 和 P 的颜色。
颜色转换
颜色转换即将子节点的颜色变为黑色,父节点的颜色变为红色。
插入
红黑树的插入与二叉查找树的插入类似,但是又比它复杂一些,可以分为五种情况。对于插入而言,被插入的节点永远是红色的,因为如果被插入的节点是黑色的,那么经过插入节点的那条路径就会必其他路径的黑色节点是多1,不满足定义 5,无论插入的位置是什么,都需要进行调整以满足定义 5。故定义新插入的节点永远为红色。
情况一
当插入节点是根节点,则将插入节点变为红色,作为根节点。
情况二
当插入节点的父节点是黑色的,则直接插入就可以,不会破坏树的定义。
情况三
当插入节点的父节点是红色的,叔节点是红色的,则插入之后需要进行颜色转换。这样一来,插入节点的祖父节点就会变成红色,可能会破坏树的定义,需要继续向上调整。
情况四
当”插入节点“的父节点是红色的,叔节点是黑色的,且”插入“在左侧,需要进行如下调整。
需要注意的是,这里的”插入节点“是加引号的,因此并不是真正意义上的插入。可以仔细想想,如果该位置的节点是直接插入的,那么 G 左右子树的黑高肯定就不一样了,这样就不满足定义 5。因此,节点 A 是由其他插入转化未而来的,如下图所示。图中的 PL 就是上图的 A。
情况五
当”插入节点“的父节点是红色的,叔节点是黑色的,且”插入“在右侧,需要进行左旋操作,这样就与情况四一样了。
删除
删除操作对红黑树来说更为复杂,与二叉查找树的删除类似,大体分以下三种情况。
-
被删除节点没有孩子节点:
- 如果删除的节点是红色的,直接删除即可。
- 如果删除的节点是黑色的,则需要进行调整。
-
被删除节点有一个孩子节点,这种情况下,孩子节点只有可能是红色的,父节点是黑色的。这样的情况也相对简单,用孩子节点替上被删除的节点,并将颜色改为黑色。不论红色节点是左节点还是右节点,删除都是一样的。
- 被删除节点有两个孩子节点,这时的删除方式就和二叉查找树很类似,需要找到这个节点的后继节点(前驱节点),然后将后继节点(前驱节点)的值复制到被删除的节点,问题转化为删除后继节点(前驱节点)。因为后继节点(前驱节点)只可能有一个孩子或者没有孩子,那么就是上述 1.1、1.2 和 2 的删除。
对于上述 1.1 和 2 都没有特别困难,因此这里只讨论情况 1.2 。在被删除节点为黑色且无孩子节点的情况下,一共有四种情况(事实上按照排列组合有 16 种情况,由定义 4 排除不可能的,得到下面几种):
- P为黑,S为黑,SL为黑,SR为黑
- P为红,S为黑,SL为黑,SR为黑
- P为黑,S为红,SR和SL为黑
- P可红可黑,S为黑,SL和SR至少有一个红色
情况一
被删除节点是黑色的,兄弟节点也是黑色的,父节点也是黑色的,兄弟节点的孩子节点也都是黑色的。此时删除节点 D,然后将 S 节点的颜色变为红色。这是通过 P 节点的路径的黑高就会少 1,需要继续向上调整直到根节点。
事实上,这里的 DL DR SL SR 只能都是 NIL 节点,因此删除 D 后 P 也可以连接 DR。更一般的来说,DL DR SL SR 的黑高必须一致。
情况二
被删除节点是黑色的,兄弟节点是黑色的,父节点是红色的,兄弟的孩子节点也都是黑色的。此时删除节点 D,调换 P 和 S 的颜色即可。
事实上,这里的 DL DR SL SR 只能都是 NIL 节点,因此删除 D 后 P 也可以连接 DR。更一般的来说,DL DR SL SR 的黑高必须一致。
情况三
被删除节点是黑色的,兄弟节点是红色的,它的兄弟节点的子节点都是黑色的。此时删除节点 D,需要以 P 为支点进行左旋,然后再以 P 为支点进行左旋,最后调换 SL 和 P 的颜色即可。
事实上,这里的 DL DR 只能都是 NIL 节点,因此删除 D 后 P 也可以连接 DR。SL SR 只能是黑高为 2 的一颗子树。更一般的来说,DL DR 的黑高比 SL SR 的黑高少 1。
情况四
被删除节点是黑色的,兄弟节点也是黑色的,但兄弟节点的左孩子是红色的,右孩子是黑色的。此时删除节点 D,需要以 P 为支点左旋,将 SR 变为黑色即可。
事实上,这里的 DL DR SL 只能都是 NIL 节点,因此删除 D 后 P 也可以连接 DR。SR 是黑高为 1 的一颗子树。更一般的来说,DL DR SL SR 的黑高必须一致。
被删除节点是黑色的,兄弟节点也是黑色的,但是兄弟节点的右孩子是红色的,左孩子是黑色的。则在情况可以转化为上述情况。此时删除节点 D,以 S 为支点右旋,即便为上述的情况。
事实上,这里的 DL DR SR 只能都是 NIL 节点,因此删除 D 后 P 也可以连接 DR。SL 是黑高为 1 的一颗子树。更一般的来说,DL DR SL SR 的黑高必须一致。
被删除节点是黑色的,兄弟节点也是黑色的,但是兄弟节点的右孩子是红色的,左孩子也是红色的。这种情况下,可以选择上述的两种方式之一即可。