红黑树原理分析

预备知识

二叉树:二叉树是每一个结点最多有两个子树的树结构;一般子树被称做“左子树”(left subtree)和“右子树”(right subtree);二叉树常被用于实现二叉查找树和二叉堆;html

平衡二叉树:又被称为AVL树(有别于AVL算法),且具备如下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,而且左右两个子树都是一棵平衡二叉树。平衡二叉树的经常使用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等;java

排序二叉树:任何节点的键值必定大于其左子树中的每个节点的键值,并小于其右子树中的每个节点的键值;算法

红黑树: spa

红黑树在原有的排序二叉树增长了以下几个要求:code

性质 1:每一个节点要么是红色,要么是黑色。
性质 2:根节点永远是黑色的。
性质 3:全部的叶节点都是空节点(即 null,其实是不存在的节点),而且是黑色的。
性质 4:每一个红色节点的两个子节点都是黑色。(从每一个叶子到根的路径上不会有两个连续的红色节点)
性质 5:从任一节点到其子树中每一个叶子节点的路径都包含相同数量的黑色节点。htm

红黑树除了具备排序二叉树特性也属于平衡二叉树,但不是严格的平衡二叉树,说它不严格是由于它不是严格控制左、右子树高度或节点数之差小于等于1,但红黑树高度依然是平均log(n),且最坏状况高度不会超过2log(n),这有数学证实。因此它算平衡树。blog

红黑树的自我调整

在树的结构发生改变时(插入或者删除操做),每每会破坏上述条件4或条件5,须要经过调整使得查找树从新知足红黑树的条件;须要经过调整使得查找树从新知足红黑树的条件。排序

调整能够分为两类:一类是颜色调整,即改变某个节点的颜色;另外一类是结构调整,集改变检索树的结构关系。结构调整过程包含两个基本操做:左旋(RotateLeft),右旋(RotateRight)递归

左旋

左旋的过程是将x的右子树绕x逆时针旋转,使得x的右子树成为x的父亲(x成为其右子树的左节点),同时修改相关节点的引用。旋转以后,二叉查找树的属性仍然知足。get

 以下,3--->9--->10这个链即是以3节点为当前节点,3节点的右子树9--->10逆时针左旋的效果,最后3成了9的左节点,三、九、10达成了平衡

 

右旋

右旋的过程是将x的左子树绕x顺时针旋转,使得x的左子树成为x的父亲(x成为其左子树的右节点),同时修改相关节点的引用。旋转以后,二叉查找树的属性仍然知足。

以下,3--->2--->1这个链即是以3节点为当前节点,3节点的左子树2--->1顺时针时针右旋的效果,最后3成了2的右节点,三、二、1达成了平衡;

 

增长节点场景分析

增长节点后有哪些场景是须要调整的?

 在一个已有的红黑树中增长一个新节点,假设3为祖父节点,2或4为父(叔)节点,新加入的节点为当前节点,当前新增节点做为2或4的子节点;能够用排除法来一个个排除,场景以下:

  1. 若是3是红节点,那么只可能出现一种状况,二、4同时存在且都是黑色节点(3不可能有红色子节点,黑色子节点不能只出现一个),2和4都为黑色节点的状况新加入的节点不管是2仍是4的子节点都不影响原有红黑树的性质,不须要调整,排除
  2. 再看,若是3节点为黑色,2或者4节点为也为黑色,那么二、4节点必须同时存在,不可能存在3只有一个黑色子节点的状况(违背了性质5);而三、二、4都为黑色节点的状况新加入的节点不管是2仍是4的子节点都不影响原有红黑树的性质,不须要调整,排除
  3. 其实如今只剩下3是黑色节点,2或4均为红色节点,这种有分为只有2节点、只有4节点、2和4同时存在,其实反过来思考,正是由于有这三种状况的存在,每当新加入一个红色节点后会引发红黑树的自我调整,调整结束后就不会有能引发调整的条件了。

 综合分析,一颗存在红黑树自己处于相对稳定状态(没有外力能触发调整),稳定的红黑树中只会存在上述无影响和待调整的结构图,不会出现不存在的结构图;而无影响的结构图新增节点并不破坏红黑树特性,因此待处理的就剩下待调整的三种了,接下来分析这三种。

注意,看图时注意结构,不要盯着纯数字,由于不一样场景,数字表明的节点含义不同

场景一:祖父节点黑色、父节点为祖父节点的左节点、无叔叔节点

这种状况,若是是孙节点做为父节点的右节点加入,对应1.1开始;若是孙节点做为父节点的左节点加入,对应1.2开始;

  • 当前节点:2
  • 当前节点的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的右孩子
  •  将“父节点”做为“新的当前节点”
  •  以“新的当前节点”为支点进行左旋
  • ----------------------------------分割线,若是从图1.2开始,只有如下步骤------------------------------------
  • 当前节点:1
  • 当前节点的父节点是红色,叔叔节点是黑色(不存在就是黑色,红黑色性质三),且当前节点是其父节点的左孩子
  •  将“父节点”设为“黑色”
  •  将“祖父节点”设为“红色”
  •  以“祖父节点”为支点进行右旋

     场景二:祖父节点黑色、父节点为祖父节点的右节点、无叔叔节点

 这种状况,若是是孙节点做为父节点的左节点加入,对应2.1开始;若是孙节点做为父节点的右节点加入,对应2.2开始;

  • 当前节点:4
  • 当前节点的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的左孩子
  • 将“父节点”做为“新的当前节点”
  • 以“新的当前节点”为支点进行右旋
  • -------------------------------------分割线,若是从图2.2开始,只有如下步骤-------------------------------------------
  • 当前节点:5
  • 当前节点的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的右孩子
  • 将“父节点”设为“黑色”
  •  将“祖父节点”设为“红色”
  • 以“祖父节点”为支点进行左旋

     场景三:祖父节点黑色、父节点红色、叔叔节点红色

这种状况,若是不管孙节点是做为父节点的左节点仍是右节点,或者不管父节点是哪个红色节点,处理方式都是统一的

  • 当前节点:1
  • 当前节点的父节点是红色,且当前节点的祖父节点的另外一个子节点(叔叔节点)也是红色
  • 将“父节点”设为黑色
  • 将“叔叔节点”设为黑色
  • 将“祖父节点”设为“红色
  • 将“祖父节点”设为“当前节点”(红色节点);即,以后继续对“当前节点”进行操做

      这种场景下,最后一步,祖父节点变成了红色,而祖父节点的父节点可能以前也是红色的,因此可能违背了红黑树性质,因此才会有最后一步将祖父节点设为“当前节点”,而后就成为了场景一和二的状况,这是一个递归的过程直到当前节点的父节点颜色是黑色。

参考java中TreeMap的实现,来看是否和上述分析一致

fixAfterInsertion是每次向TreeMap中新增节点后都会调用的修正方法,正式这个方法保证和红黑树的性质,与之对应的有fixAfterDeletion(删除节点后调用)
看这个方法的逻辑,彻底与上述分析一致。
 1     private void fixAfterInsertion(Entry<K,V> x) {
 2     // 新增节点都是红色
 3         x.color = RED;
 4 
 5         // 递归处理,当前节点的父节点是红色就要一直处理
 6         while (x != null && x != root && x.parent.color == RED) {
 7             // 父节点为祖父节点的左节点
 8             if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
 9                 Entry<K,V> y = rightOf(parentOf(parentOf(x)));
10                 if (colorOf(y) == RED) {
11                     // 叔叔节点为红色,父、叔节点置黑,祖父节点置红,以祖父节点为当前节点递归
12                     setColor(parentOf(x), BLACK);
13                     setColor(y, BLACK);
14                     setColor(parentOf(parentOf(x)), RED);
15                     x = parentOf(parentOf(x));
16                 } else {
17                     if (x == rightOf(parentOf(x))) {
18                         // 父节点左旋为祖父节点的右旋腾出位置(孙父祖三代处于同一斜率,依次为左子节点)
19                         x = parentOf(x);
20                         rotateLeft(x);
21                     }
22                     // 父节点置黑、祖父节点置红、祖父节点右旋
23                     setColor(parentOf(x), BLACK);
24                     setColor(parentOf(parentOf(x)), RED);
25                     rotateRight(parentOf(parentOf(x)));
26                 }
27             } 
28             // 父节点为祖父节点的右节点
29             else {
30                 Entry<K,V> y = leftOf(parentOf(parentOf(x)));
31                 if (colorOf(y) == RED) {
32                     // 叔叔节点为红色,父、叔节点置黑,祖父节点置红,以祖父节点为当前节点递归
33                     setColor(parentOf(x), BLACK);
34                     setColor(y, BLACK);
35                     setColor(parentOf(parentOf(x)), RED);
36                     x = parentOf(parentOf(x));
37                 } else {
38                     if (x == leftOf(parentOf(x))) {
39                         // 父节点右旋为祖父节点的左旋腾出位置(孙父祖三代处于同一斜率,依次为右子节点)
40                         x = parentOf(x);
41                         rotateRight(x);
42                     }
43                     // 父节点置黑、祖父节点置红、祖父节点左旋
44                     setColor(parentOf(x), BLACK);
45                     setColor(parentOf(parentOf(x)), RED);
46                     rotateLeft(parentOf(parentOf(x)));
47                 }
48             }
49         }
50         
51         // 根节点置黑
52         root.color = BLACK;
53     }

思考

为甚麽红黑树中新增长的节点必定是红色?

       红黑树的5个性质中,性质4和性质5是比较容易违背的,为了尽可能避免由于破坏红黑树的特性而作调整,每次新插入的节点都是红色。由于插入以前全部根至外部节点的路径上黑色节点数目都相同,若是插入的节点是黑色确定错误(黑色节点数目不相同),而相对的插入红节点可能会也可能不会违反“没有连续两个节点是红色”这一条件,因此插入的节点为红色代价相对小,若是违反条件再调整。

为何基于“子节点-->父节点-->祖父节点”来调整红黑树的平衡?     

       在进行颜色变化或旋转的时候,每每要涉及祖孙三代节点(X表示操做的基准节点,P表明X的父节点,G表明X的父节点的父节点);这是由于基于至少三个节点来旋转调色能够尽可能保持局部知足红黑树的5个特性,这样就能尽可能不破坏总体特性;若是只有两个节点子和父,就算知道破坏了红黑树的性质也无法经过自我调整来达到效果,只有两个节点旋转来旋转去也不平衡。

为何场景一和场景二中,孙节点做为父节点的左节点和右节点处理场景不同?

      拿场景一来讲,若是不将下图中的1和2节点进行一次左旋,那么3节点在右旋的时候2会成为3的左节点,不能同时保证性质4和5;假设红黑树只有三个节点,右旋以后1成为根节点,3是1的右节点,2是3的左节点;因为1是黑色且3只能为红色,那么2节点为黑色破坏了性质5,2节点为红色破坏了性质4;

有人问为何不把3往左旋,其实仔细想一想红黑树的默认排序规则,一个节点的值必定大于其左子树小于其右子树。所以,保证孙--->父--->祖三代节点都在同一斜率(依次为左子节点或依次为右子节点)能够同时知足红黑树的五种性质而且尽量保证红黑树的平衡。

 

删除节点场景分析

待补充

 

PS:在线生成红黑树链接:https://sandbox.runjs.cn/show/2nngvn8w

 参考:

http://www.javashuo.com/article/p-orraekjs-n.html

http://www.cnblogs.com/CarpenterLee/p/5525688.html

相关文章
相关标签/搜索