0.目录
- 1.算法导论的红黑树本质上是2-3-4树
- 2.红黑树的结构和性质
- 3.红黑树的插入
- 4.红黑树的删除
- 5.基于2-3-4树的左倾红黑树
- 6.Sedgewick改进的一种更简单的红黑树——基于2-3树的左倾红黑树
相关总结参见:http://www.jianshu.com/p/bdd7442f54e2
- 红黑树(Red Black Tree) 是一种自平衡二叉查找树,是二叉查找树的变种之一。它是在1972年由Rudolf Bayer发明的,当时被称为平衡二叉B树(symmetric binary B-trees)。后来,在1978年被 Leo J. Guibas 和 Robert Sedgewick修改为如今的“红黑树”。 2008年 Robert Sedgewick 对其进行了改进,并命名为 LLRBT(Left-leaning Red Black Tree 左倾红黑树)。左倾红黑树相比1978年的红黑树要简单很多,实现的代码量也少很多。Robert Sedgewick也是Algorithms(中文版叫《算法》)这本书的作者,在这本书中就讲了基于2-3树的左倾红黑树。
- 现在的使用的工程代码中的红黑树都是基于78年的算法,比如JDK中的TreeMap。其实红黑树就是2-3-4树的具体实现,所以要想理解红黑树就得先理解2-3-4树。而08年左倾红黑树则是基于2-3树。
1.算法导论的红黑树本质上是2-3-4树
1.1 2-3-4树的定义
一棵2-3-4树是一棵完美平衡的2-3-4查找树。
- 从根到所有叶子节点(NULL叶子)距离都是相同的
-
允许2-结点、3-结点、4-结点
2-结点:一个健,两个孩子
3-结点:两个键,三个孩子
4-结点:三个键,四个孩子
2-3-4树优良的平衡性
关键的属性:所有从根到叶子(NULL节点)的距离是一样长
树高:
- 最差情况:lgn (全部是2-结点)
- 最好情况: 1/2lgn(全部是4-结点)
直接实现2-3-4树是复杂的,使用红黑树代替实现。
1.2 2-3-4树的操作
1.2.1 查找
-
递归自上而下同结点中的键进行比较,向下找到相关的路径,直至找到为止
1.2.2 插入
-
插入一个二结点——变成三结点
-
插入一个三结点——变成四结点
-
插入一个四结点——分裂四结点
有两种方法(对应第三、第四两节):
- Bottom-up solution (Bayer, 1972)
使用同样的方法分裂父节点
如有需要,沿着树一直向上进行此操作 - Top-down solution (Guibas-Sedgewick, 1978)
沿路向下时,分裂遇到的4-结点
到达底部时,直接插入
1.2.2.1 自底向上插入
- step1.如果2-3-4树中已存在当前插入的key,则插入失败,否则最终一定是在叶子节点中进行插入操作
- step2.如果待插入的节点不是4节点,那么直接在该节点插入
- step3.如果待插入的节点是个4节点,那么应该先分裂该节点然后再插入。一个4节点可以分裂成一个根节点和两个子节点(这三个节点各含一个key)然后在子节点中插入,我们把分裂形成的根节点中的key看成向上层插入的key,然后重复step2和step3。
如果是在4节点中进行插入,每次插入会多出一个分支,如果插入操作导致根节点分裂,则2-3-4树会生长一层。
1.2.2.2 自顶向下插入
核心是保证当前结点不是4-结点。
有两个结果:
- 上下连续两个4-结点不会出现(归纳法:根节点不会是,否则分解成三个2-结点,树高增1;当前结点不会是,要么是2-结点,要么是3-结点;如果下一层是四结点,则进行分解,则下一层要么是2-结点,要么是3-结点,而下一层的父节点可能是4-结点,但是下一层已经不是了,所以不会出现连续两个4-结点)
- 最底层的结点要么是2-结点,要么是3-结点
分裂四结点的方法:
因为不会出现上下两个连续的4-结点,因此4-结点只可能出现在2-结点或者3-结点下面:
-
分裂2-结点下面的4-结点
-
分裂3-结点下面的4-结点
-
自顶向下插入的示例——树向上增长
另一个例子
1.2.3 删除
任何一个结点的删除,都可以归结为后继结点(一个叶子节点,一定是一个叶子节点,否则在该节点临近的右分支树上还有更小的数)的删除:
- 16结点的删除,可以将其后继,也即叶子节点中的17代替16,然后归结为删除叶子节点的17
- 23结点的删除,可以归结为后继叶子节点25结点的删除
- 50结点的删除,可以归结为后继叶子节点55结点的删除
1.2.3.1 自底向上删除
- step1.如果2-3-4树中不存在当前需要删除的key,则删除失败。
- step2.如果当前需要删除的key不位于叶子节点上,则用后继key覆盖,然后在它后继key所在的子支中删除该后继key。
-
step3. 如果当前需要删除的key位于叶子节点上:
step3.1 该节点不是2节点,删除key,结束
step3.2 该节点是2节点,删除该节点:
step3.2.1 如果兄弟节点不是2节点,则父节点中的key下移到该节点,兄弟节点中的一个key上移
step3.2.2 如果兄弟节点是2节点,父节点是个3节点或4节点,父节点中的key与兄弟节点合并
step3.2.3 如果兄弟节点是2节点,父节点是个2节点,父节点中的key与兄弟节点中的key合并,形成一个3节点,把此节点看成当前节点(此节点实际上是下一层的节点),重复步骤3.2.1到3.2.3
如果是在2节点(叶子节点)中进行删除,每次删除会减少一个分支,如果删除操作导致根节点参与合并,则2-3-4树会降低一层。(只有重复step3.2.3,直至根节点,才会发生根的高度减小一层,如下图所示示例)
1.2.3.2 自顶向下删除
自顶向下删除时,保证当前结点不是2-结点(除根节点,因为根节点没有兄弟节点和父节点)。
遇到当前节点是2节点,如果兄弟节点也是2节点就合并(该节点的父节点中的key下移,与自身和兄弟节点合并);如果兄弟节点不是2节点,则父节点的key下移,兄弟节点中的key上移。这样可以保证,找到需要删除的key所在的节点时可以直接删除(即要删除的key所在的节点一定不是2节点)。
如下示例中,如果删除根节点50怎么办?
方法同自底向上删除一样,用后继55代替50,然后转换为删除55。即同样转换为删除叶子节点,然后自顶向下保证当前结点不为2-结点。
这里包含key为60的节点也可以选择让父节点中的key 76下移和兄弟节点中的83合并,两种方式都能达到B树的平衡,这也是在2-3-4树对应的红黑树中使用的方式。
1.2.4 红黑树与2-3-4树的等价性
2.红黑树的结构和性质
红黑树是一颗二叉搜索树,它在每个结点上增加了一个存储位来表示结点的颜色,可以是red或black。
通过对任何一条从根到叶子的简单路径上各个结点的颜色进行约束,红黑树确保没有一条路径会比其他路径长处2倍,因而近似于平衡的。
结点属性:color key left right p
可以把NIL视为二叉搜索树的叶节点的指针,而把带关键字的结点视为树的内部结点。
一棵红黑树是满足下面红黑性质的二叉搜索树:
- 1.每个结点或是红色的,或是黑色的
- 2.根节点是黑色的
- 3.每个叶节点NIL时黑色的(这条性质可去掉)
- 4.如果一个结点是红色的,则它的两个子节点都是黑色的
- 5.对每个结点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色结点
3.红黑树的插入
插入的要求:保持2-3-4树完全平衡,即根到所有的叶子(NULL结点)的距离完全相等,因此必须要求插入的是红色结点。且2-3-4树只能向上增长。(因此必须保证当前结点不是4-结点)
另外4-结点的形式如下:
以下形式不允许:
1)插入2-结点下面,直接插入(红色),变成3-结点
2)插入3-结点下面,直接插入(红色),变成4-结点
3)插入4-结点下面,需要对4-结点进行分裂
对于4-结点进行分裂,会让孩子结点的元素插入到父节点,如果父节点此时是4-结点,则会导致又一次分裂,因此针对这种情况,有两种处理方法:
- Bottom-up solution (Bayer, 1972)
使用同样的方法分裂父节点
如有需要,沿着树一直向上进行此操作 - Top-down solution (Guibas-Sedgewick, 1978)
沿路向下时,分裂遇到的4-结点(保证当前结点不是4-结点)
到达底部时,直接插入
3.1 自底向上的插入(与1.2.2.1 完全平衡树2-3-4树的自底向上插入一一对应)
将新元素插入到红黑树的底部(作为叶子),并标记为红色。
因此只可能违反性质2或性质4,然后自底向上调整红黑树以修复性质,z和z.p都是红色(主要是性质4,性质2可以通过重新着色完成修复):
-
情况1.z的叔结点y是红色的(等同于插入一个4-结点)
*情况2.z的叔结点y是黑色的,且z是一个右孩子(等同于插入一个3-结点)
*情况3.z的叔结点y是黑色的,且z是一个左孩子(等同于插入一个3-结点)
3.2 自顶向下的插入(与1.2.2.2 完全平衡树2-3-4树的自顶向下插入一一对应)
插入的结点还是插入在叶子上,且颜色还为红色,但是保证新插入结点的父节点为黑色,所以在向下的路径中,需要做一些调整(只有一个调整):
-
沿路向下找到新元素的插入位置(叶节点)时,如果碰到一个结点X有两个红儿子,则让X变为红的,两个儿子变为黑的(等同于分裂4-结点)
此时可能X和X.p都为红的,则将X X.p X.p.p做旋转,此时X的叔结点一定是黑色的,因为从上到下的过程中已经排除了有两个儿子同为红色的可能性。此时X.p是红色的,那么X的叔结点一定是黑色的。(通过下图恢复——等同于调整4-结点为对称形式,因为4-结点只允许对称形式,这个4-结点是父节点(原来是3-结点),且是因为孩子4-结点分裂上移一个元素成为的4-结点)
例子如下:
-
step1.插入序列为10,85,15,70,20,60,30,50,65,80,90,40,5,55,形成如下红黑树:
-
step2.自顶向下的路径为,其中结点50有两个红儿子
-
step3.重新着色,并调整结构,使其满足红黑树性质
-
step4.自顶向下插入实现时,要注意如果插入的结点的是3,应当如何处理,这时候,再进行一次旋转即可。3,5都是红色,且3的叔结点一定是黑色(NULL)
4.红黑树的删除
任何一个结点的删除,都可以归结为后继结点(一个叶子节点,一定是一个叶子节点,否则在该节点临近的右分支树上还有更小的数)的删除:
- 16结点的删除,可以将其后继,也即叶子节点中的17代替16,然后归结为删除叶子节点的17
- 23结点的删除,可以归结为后继叶子节点25结点的删除
- 50结点的删除,可以归结为后继叶子节点55结点的删除
删除的要求:保持2-3-4树完全平衡,即根到所有的叶子(NULL结点)的距离完全相等,因此自顶向下设法保证删除的是红色结点(也即保证当前结点不是2-结点)。
1)从叶子4-结点中删除,直接删除,变成3-结点
2)从叶子3-结点中删除,直接删除,变成2-结点
3)从叶子2-结点中删除,需要转换成3-结点或4-结点进行删除(具体参见1.2.3.1 自底向上删除)
4.1 自底向上的删除
4.1.1 一个重要结论:如果一个红黑树结点只有一个儿子,或者没有儿子,则一定是对应2-3-4树叶子节点
- 没有儿子,肯定是叶子节点
-
只有一个儿子,则该节点必然是黑色的,且儿子是红色的,则必然是2-3-4树的叶子节点
4.1.2 自底向上删除分为如下四种情况,对这四种情况进行分析(情况1和2可以算作一种,本身z就是对应2-3-4树叶子节点;情况3和4可以算作一种,都是找后继y(对应2-3-4树叶子节点))
-
1)如果z没有左孩子,那么用其右孩子来代替z,这个右孩子可以是NIL,也可以不是。
-
2)如果z仅有一个孩子且是其左孩子,用左孩子来代替z
-
3)否则,z既有一个左孩子又有一个右孩子。我们要查找z的后继y,这个后继位于z的右子树中并且没有左孩子。现在需要将y移出原来的位置进行拼接,并替换树中的z
如果y是z的右孩子,那么用y替换z,并仅留下y的右孩子。
-
4)否则,y位于z的右子树中但并不是z的右孩子。此时先用y的右孩子替换y,然后再用y替换z。
总结:
情况1和2——删除的都是叶子节点上的值,此时z和y相同,但是此叶子是2-结点和3-结点两种情况,对于另一种4-结点情况的删除,包含在情况3中
情况3和4——本质上都是一样的,寻找z的后继y来代替z,然后删除y;删除y已经是叶子,转换为情况1和2和情况3的叶子删除情况
z——删除的真实结点
y——最后都归结为对结点y的删除:y是被转换为叶子节点中要删除的结点,如果z本身是叶子节点,则y=z;否则y是z的后继
x——x和y同属于一个叶子结点
- 如果y原来的颜色为红色,红黑树的性质保持,不用进行调整(因为y是叶子节点中的红结点,可以随意删除)
- 如果y原来的颜色为黑色,会违反性质
1)y是根节点,红色的x成为新根,违反性质2(y既是根节点又是叶子节点,此时只存在一个结点,将x作为根,涂成黑色即可)
2)x和x.p是红色,违反性质4(此时x是红色,将x涂成黑色即可)
3)移动y导致违反性质5(此时x是黑色,根据上面的情况可知x是T.nil)
总结:
- 1)2)表示y所在的叶子节点是3-结点或者4-结点,删除黑结点后,只要让红色的x成为新的黑结点即可
- 3)此时x是T.nil,y是一个2-结点,删除2-结点会有问题
(重要)对后面的四种情况进行总结:
- 情况1对应的父节点是3-结点或4-结点,对应下面情况2中的1)和情况3、情况4,不做实质性处理
- 情况2对应两种情况:
1)父节点是3-结点或4-结点,兄弟节点是2-结点,对应《1.2.3.1 自底向上删除》step3.2.2
2)父节点和兄弟节点都是2-结点,对应《1.2.3.1 自底向上删除》step3.2.3 - 情况3和情况4对应兄弟节点是3-结点或4-结点,对应《1.2.3.1 自底向上删除》step3.2.1
当x是黑色时,分为以下四种情况进行调整:
关键思想是在每种情况中,从子树的根(包括根)到每棵子树αβγδεζ之间的黑结点个数(包括x的额外黑色)并不被变换改变。
-
情况1:x的兄弟节点是红色(对应父节点不是2-结点,情况1没有任何实质性的处理,只是将父节点从右倾变为左倾)
此时,各个子树的黑结点并未变换;且通过修改颜色和左旋转换为情况2、3、4
-
情况2:x的兄弟节点w是黑色的,而且w的两个子节点都是黑色的(此时2-3-4树中的兄弟节点是2-结点,根据2-3-4中父节点是否是2-结点进行处理)
将x和w上去掉一重黑色,x只有一重黑色而w为红色。并在x.p上新增一重额外的黑色,将x.p作为新的x来重复while循环。
如果是情况1进入到情况2,因为x.p是红色,则新x是红黑色,循环直接终止,将新x着为(单一)黑色。
父节点为3-结点或4-结点:
父节点和兄弟节点均为2-结点:
-
情况3:x的兄弟节点w是黑色,w的左孩子是红色,w的右孩子时黑色(此时2-3-4树中的兄弟节点不是2-结点;情况3没有做任何处理,只是将左倾变为右倾)
通过交换w和w.left的颜色,对w进行右旋,将情况3转换成了情况4
-
情况4:x的兄弟w是黑色,且w的右孩子是红色的(此时2-3-4树中的兄弟节点不是2-结点)
此种情况可以去掉x的额外的黑色(无论x.p的颜色是黑色还是红色,都成立)
4.2 自顶向下的删除
核心思路:将删除归结为删除一片红叶子。(保证删除的不是2-结点)
总是可以归结为一个2-3-4树叶子节点中关键字的删除:
- 本身是叶子节点则满足
- 如果不是,寻找后继,用后继值代替该节点,则转换为删除后继结点,并且后继一定在一个2-3-4树的叶子节点中
保证删除的不是2-结点?
遇到当前节点是2节点,如果兄弟节点也是2节点就合并(该节点的父节点中的key下移,与自身和兄弟节点合并);如果兄弟节点不是2节点,则父节点的key下移,兄弟节点中的key上移。这样可以保证,找到需要删除的key所在的节点时可以直接删除(即要删除的key所在的节点一定不是2节点)。
这里可以确定:向下搜索的路径上,当前结点的父节点一定3-结点和4-结点。
(重要)对下面情况的总结:
- 情况1-1)对应兄弟节点也是2节点就合并(该节点的父节点中的key下移,与自身和兄弟节点合并
- 情况1-2)和1-3)对应兄弟节点不是2节点,则父节点的key下移,兄弟节点中的key上移
- 情况2) X本身就不是2-结点,直接下降
《数据结构与算法分析》给出的类似方法:
怎样确保从上到下查找期间树叶是红的?
假设当前结点为X,T是其兄弟,P是其父亲
- 开始时把树根涂成红色(当两个儿子中有一个为红时,直接下降,不用将根涂成红色,否则有两个连续的红结点,违反性质4),当沿树向下遍历时,设法保证X是红色的
-
当我们到达一个新结点,要确信P是红的(根节点除外),并且X和T是黑色的
1)X有两个黑儿子
1-1)T有两个黑儿子(让2-结点变成4-结点)
1-2)T的左儿子是红色的(父节点的key下移,兄弟节点中的key上移)
1-3)T的右儿子是红色的(父节点的key下移,兄弟节点中的key上移)
2)X的儿子之一是红的(本身就是3-结点或4-结点,直接下降)
我们落到下一层,得到新的X、T和P。
2-1)X落到红色的孩子上面,则可以继续下降
2-2)X落在黑色的孩子上面
旋转过后,又回到了最初的情况,P是红色的,X和T是黑色的。
实例
证明:为什么各种情况下通过相应的旋转和重新着色可以使得满足红黑树性质?
证明:确保旋转和着色后保证红黑树各条性质都满足(特别是性质4和5:不能有连续的两个红结点,以及结点到所有叶子节点的黑色结点个数是相等)