zoukankan      html  css  js  c++  java
  • [转载]数据结构树之红黑树

    本文转载自水目沾博客:http://www.cnblogs.com/zhuwbox/p/3634895.html

    红黑树简介:

      红黑树是一棵二叉搜索树,它在每个结点上增加了一个存储位来表示结点的颜色,可以是RED 或 BLACK。通过对任何一条根到叶子的简单路径上各个结点的颜色进行约束,红黑树确保没有一条路径会比其他路径长2倍,因而是近似平衡的。

      树的每个结点包含 5 个属性:color,key,left,right和p。如果一个结点没有子结点或者父结点,则该结点相应的指针属性的值为NULL。我们可以把这些NULL视为指向二叉搜索树叶结点的指针,而把带关键字的结点视为树的内部结点。

    红黑树的性质:

      一棵红黑树是满足下面红黑性质的二叉搜索树:

      1.每个结点或是红色的,或是黑色的

      2.根节点是黑色的

      3.每个叶结点(NULL)是黑色的

      4.如果一个结点是红色的,那么他的两个子结点都是黑色的

      5.对于每个结点,从该结点到其所有后代叶结点的简单路径上,包含相同数目的黑色结点

      这 5 个性质中1,2,4都比较好理解。3与我们常说的(大部分数据结构书上说的)叶结点有一点点区别,如下图:

      

    那性质5又是什么意思呢?我们再来看一个图:

      

      由红黑树的 5 个性质可知,上幅图中左图是红黑树,而右图非红黑树。右图中满足红黑树的性质1.2.3.4,但是不满足性质5:从根节点6(不包括根节点)到各叶结点的简单路径上的黑色黑色结点个数并不相等。例如:6-1有2个,而6-8和6-10都是有三个。

      这些约束强制了红黑树的关键性质: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这个树大致上是平衡的。因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上的理论上限允许红黑树在最坏情况下都是高效的,而不同于普通的二叉查找树。

    要知道为什么这些特性确保了这个结果,注意到属性4导致了路径不能有两个毗连的红色节点就足够了。最短的可能路径都是黑色节点,最长的可能路径有交替的红色和黑色节点。因为根据属性5所有最长的路径都有相同数目的黑色节点,这就表明了没有路径能多于任何其他路径的两倍长。

    在很多树数据结构的表示中,一个节点有可能只有一个子节点,而叶子节点包含数据。用这种范例表示红黑树是可能的,但是这会改变一些属性并使算法复杂。为此,本文中我们使用 "nil 叶子" 或"空(null)叶子",如上图所示,它不包含数据而只充当树在此结束的指示。这些节点在绘图中经常被省略,导致了这些树好像同上述原则相矛盾,而实际上不是这样。与此有关的结论是所有节点都有两个子节点,尽管其中的一个或两个可能是空叶子。

    红黑树的操作:

      因为每一个红黑树也是一个特化的二叉查找树,因此红黑树上的只读操作与普通二叉查找树上的只读操作相同。然而,在红黑树上进行插入操作和删除操作会导致不再符合红黑树的性质。恢复红黑树的属性需要少量(O(log n))的颜色变更(实际是非常快速的)和不超过三次树旋转(对于插入操作是两次)。虽然插入和删除很复杂,但操作时间仍可以保持为 O(log n) 次。我们在这只讲讲红黑树的插入和删除。

      1.插入

      下面看看算法导论中给的伪代码:

    复制代码
     1 /*
     2 注意以下的T.nil,是一个与普通红黑树结点相同的对象。他的color是BLACK,他也是根节点的父节点
     3     RB-INSERT(T,z)                  //向树T中增加结点z
     4     y = T.nil                      //根节点的父节点
     5     x = T.root                      //根节点
     6     while x != T.nil                //while循环内是为了寻找插入结点z的位置
     7         y = x                       //y始终是x的父节点
     8         if z.key < x.key
     9             x = x.left
    10         else 
    11             x = x.right
    12     //跳出while循环之后,说明y结点的某个孩子是T.nil了,可以插入了!
    13     z.p = y                        //z的父结点是y
    14     if y == T.nil                  //如果y就是 T.nil说明该树为空,插入z后,z就是根节点
    15         T.root = z
    16     else if z.key < y.key          //如果z比y结点值小,则插到y的左孩子上
    17         y.left = z
    18     else 
    19         y.right = z                //否则插到y的右孩子上
    20     z.left = T.nil
    21     z.right = T.nil                //将z的左右孩子都设为T.nil
    22     z.color    = RED               //z的颜色设为红色
    23     RB-INSERT-FIXUP(T,Z)           //插入一个红色结点会破坏红黑树的性质,需要调整
    24  */
    复制代码

      比如我们插入一个值为3的结点:在RB-INSERT-FIXUP函数执行之前,执行的结果如下图:

       由上图可以看出T.nil的作用是充当一个哨兵,它也是一个红黑树结点对象,且颜色为黑色,其他的值任意!插入3,并将3的颜色涂成红色之后,有可能会破坏红黑树的性质2和4(上图就破坏了性质5).所以我们要调用RB-INSERT-FIXUP来保持红黑树的性质。RB-INSERT-FIXUP的伪代码如下:

      

    复制代码
     1 /*
     2     以下是实现RB-INSERT-FIXUP(T,Z)伪代码
     3     while z.p.color == RED                //因为z本身是红色,如果他的父结点是红色那这个循环就要继续---调节树
     4         if z.p == z.p.p.left              //如果z的父亲是z祖父的左孩子
     5             y = z.p.p.right               //令y为z祖父的右孩子,也就是说y是z的叔叔
     6             if y.color == RED             //如果y的颜色是红色
     7                 z.p.color = BLACK         //case 1    既然z是红色,为了不破坏性质4,将z的父节点涂成黑色
     8                 y.color   = BLACK         //case 1    同时也要讲z的叔叔结点涂成黑色
     9                 z.p.p.color=RED           //case 1    同时将z的祖父结点(y的父节点)涂成红色
    10                 z = z.p.p                 //case 1    令z 等于 z的祖父,循环继续
    11             else if z == z.p.right        //如果z是父结点的右孩子
    12                 z = z.p                   //case 2    z等于z的父结点
    13                 LEFT-ROTATE(T,Z)          //case 2    右旋
    14             z.p.color = BLACK             //case 3    将z的父结点颜色涂成黑色
    15             z.p.p.color = RED             //case 3    将z的祖父结点涂成红色
    16             RIGHT-ROTATE(T,Z.P.P)         //case 3    右旋
    17         else(same as then clause with 'right' and 'left' exchanged)
    18     T.root.color = BLACK
    19  */
    复制代码

      这里伪代码里面有两个函数要注意下,LEFT-ROTATE() 和 RIGHT-ROTATE().这个分别是左旋和右旋的函数。左旋和右旋的过程我已经在我的另一篇博客中用图解释的很清楚了:http://www.cnblogs.com/zhuwbox/p/3636783.html

      

    下面是左旋的伪代码:

    复制代码
     1 /*
     2 LEFT-ROTATE(T,x)--参考上图
     3     y = x.right            //给y赋值
     4     x.right = y.left                //将x的右结点指向y的左结点
     5     if y.left != T.nil        
     6         y.left.p = x        //设置y左结点的父节点为x
     7     y.p = x.p                //y的父结点是x的父节点
     8     if x.p == T.nil            //如果 x 是根节点
     9         T.root = y;
    10     elseif x == x.p.left    //如果x是父结点的左孩子
    11         x.p.left = y;        //
    12     else x.p.right = y        //如果x是父结点的右孩子
    13     y.left = x;                //y的左孩子是x
    14     x.p = y                    //x的父节点是y
    15 */    
    复制代码

      RB-INSERT-FIXUP要处理的情况有三种。

      a).情况一:插入结点后的结点z。z和父结点都是红色,违反性质4.如下图:

       解决方法是:将z的父结点和叔叔结点涂成黑色,并且z的指针沿z树上升(对应RB-INSERT-FIXUP代码中的case 1部分)。所得情况如下图

       b).情况二:调整后的结点z(此时是7)和父结点(结点2)都是红色,但是叔叔结点(结点1)是黑色,此时出现情况二。解决方法:将2作为根节点T进行左旋。得到如下图:

      c).情况三:调整后的结点z(此时是2)和父结点是红色,但是叔叔结点(8)是黑色。要进行如下操作:将z结点的父结点涂成黑色,将z的祖父结点涂成红色。再以z的父结点为根T,作一次右旋转即可得到一棵合法的红黑树,如下图:

      此时的z的父节点不再是红色,退出while循环(如果不退出循环,情况肯定是这三种中的一种)。一棵合法的红黑树形成!

  • 相关阅读:
    springMVC的@RequestMapping的参数和用法
    springMVC的helloword
    eclipse 导入MyEclipse创建的web项目该如何启动?
    解决mysql中文乱码
    java核心技术 卷1 基础知识—对象与继承
    Java核心技术 卷1 基础知识—概述、环境
    python爬虫框架之scrapy的快速上手
    如何评测软件工程知识技能水平
    金融文本挖掘的业务领域建模
    金融文本挖掘中的针对业务的用例建模
  • 原文地址:https://www.cnblogs.com/HXloveLL/p/3680039.html
Copyright © 2011-2022 走看看