工大人在上海
做为c++菜鸟也要嗷嗷飞!

   这两天继续研究RB_Tree。

   上次说到为什么sgi stl会选择rb_tree作为set,map的基础实现,而不选择avl_tree。经过对资料的查询,最终,得出的结论是rb_tree在rebalance方面右性能优势。rb_tree的rebalance的时间顶多是进行两次totate,而avl_tree就需要有O(log(N))的时间。
   那么,这次我们先来研究为什么rb_tree的rebalance函数只需要2次rotate就能够解决。
   其实问题的答案就在sgi版本的stl源码中,让我们对stl中stl_tree.h进行解析,就不难找出答案。
   sgi版本的stl中stl_tree.h实现了rb_tree的迭代器,数据结构以及各种操作。
   为了更大的弹性,sgi将rb_tree的节点结构以及迭代器都实现为两层(与slist类似)。如图所示:

   而设计中四个类全部使用struct进行设计,所以所有成员都是public的。

   进行插入操作之后,rb_tree都会进行一次调整操作,将树的状态调整到符合rb_tree的要求。此时就会调用我们今天的主角__Rb_tree_rebalance()函数。此函数解析如下:

 //全局函数
  
//重新令树形平衡(改变颜色及旋转树形)
  
//第一个参数为新增节点
  
//第二个参数为root
  inline void 
  _Rb_tree_rebalance(_Rb_tree_node_base
* __x, _Rb_tree_node_base*& __root)
  
{
    __x
->_M_color = _M_red;                       //新节点的颜色一定是红色
    while (__x != __root 
       
&& __x->_M_parent->_M_color == _M_red)     //父节点为红
      {
    
if (__x->_M_parent == __x->_M_parent->_M_parent->_M_left)    //父节点为祖父节点之左子节点
      {
        _Rb_tree_node_base
* __y = __x->_M_parent->_M_parent->_M_right;    //令__y为伯父节点
        if (__y && __y->_M_color == _M_red)                               //伯父节点存在而且为红
          {
        __x
->_M_parent->_M_color = _M_black;                              //将父节点的颜色改成黑色
        __y->_M_color = _M_black;                                          //将伯父节点的颜色改成黑色
        __x->_M_parent->_M_parent->_M_color = _M_red;                     //将祖父节点的颜色改成红色
        __x = __x->_M_parent->_M_parent;                                  //将__x设置成祖父节点
          }

        
else                     //如果没有伯父节点,或伯父节点为黑色
          
        
if (__x == __x->_M_parent->_M_right)          //如果新节点是父节点的右子节点
          {
            __x 
= __x->_M_parent;                     //将新节点赋值成其父节点
            _Rb_tree_rotate_left(__x, __root);        //将树做左旋转,第一个参数是左旋点
          }

        __x
->_M_parent->_M_color = _M_black;          //将父节点的颜色更改成黑色
        __x->_M_parent->_M_parent->_M_color = _M_red;  //将祖父节点的颜色更改成红色
        _Rb_tree_rotate_right(__x->_M_parent->_M_parent, __root);  //将树做右旋转,第一个参数是右旋点
          }

      }

    
else          //如果父节点是祖父节点之右子节点
      {
        _Rb_tree_node_base
* __y = __x->_M_parent->_M_parent->_M_left;      //令__y为伯父节点
        if (__y && __y->_M_color == _M_red)        //如果伯父节点存在而且伯父节点的颜色是红色
          {
        __x
->_M_parent->_M_color = _M_black;       //将父节点颜色改成黑色
        __y->_M_color = _M_black;                  //将伯父节点颜色改成黑色
        __x->_M_parent->_M_parent->_M_color = _M_red;  //将祖父节点的颜色改成红色
        __x = __x->_M_parent->_M_parent;             //将新节点赋值成祖父节点
          }

        
else                   //如果没有祖父节点,或者祖父节点颜色是黑色
          {
        
if (__x == __x->_M_parent->_M_left)     //如果新节点是父节点的左子节点
          {
            __x 
= __x->_M_parent;             //将父节点的值赋值给新节点
            _Rb_tree_rotate_right(__x, __root);   //将树做右旋转,第一个参数是右旋点
          }

        __x
->_M_parent->_M_color = _M_black;     //将父节点的颜色改成黑色
        __x->_M_parent->_M_parent->_M_color = _M_red;    //将祖父节点的颜色改成红色
        _Rb_tree_rotate_left(__x->_M_parent->_M_parent, __root);    //将树做左旋转,第一个参数是左旋点
          }

      }

      }

    __root
->_M_color = _M_black;        //根节点永远都是黑色(符合RB_Tree定义)
  }



其中还会调用另外两个全局函数做左旋转以及右旋转。函数代码分析如下:
//全局函数
  
//新节点必定是红节点,如果插入处的父节点也是红节点,就违反了红黑树规则,此时必须
  
//对红黑树进行旋转(以及改变颜色)
  
//此函数为左旋转函数
  
//第一个参数是左旋点
  
//第二个参数是root
   inline void 
  _Rb_tree_rotate_left(_Rb_tree_node_base
* __x, _Rb_tree_node_base*& __root)
  
{
    _Rb_tree_node_base
* __y = __x->_M_right;            //令__y作为左旋点的右子节点
    __x->_M_right = __y->_M_left;                       
    
if (__y->_M_left !=0)
      __y
->_M_left->_M_parent = __x;                           //设定父节点
    __y->_M_parent = __x->_M_parent;
    

    
//令__y完全顶替__x的地位
    if (__x == __root)                  //__x为根节点
      __root = __y;
    
else if (__x == __x->_M_parent->_M_left)           //__x为其父节点的左子节点
      __x->_M_parent->_M_left = __y;
    
else                                                //__x为其父节点的右子节点
      __x->_M_parent->_M_right = __y;                 
    __y
->_M_left = __x;
    __x
->_M_parent = __y;
  }



  
//全局函数
  
//新节点必定是红节点,如果插入处的父节点也是红节点,就违反了红黑树规则,此时必须
  
//对红黑树进行旋转(以及改变颜色)
  
//此函数为右旋转函数
  
//第一个参数是右旋点
  
//第二个参数是root
  inline void 
  _Rb_tree_rotate_right(_Rb_tree_node_base
* __x, _Rb_tree_node_base*& __root)
  
{
    _Rb_tree_node_base
* __y = __x->_M_left;             //令__y作为右旋点的左子节点
    __x->_M_left = __y->_M_right;
    
if (__y->_M_right != 0)
      __y
->_M_right->_M_parent = __x;                   //设定父节点
    __y->_M_parent = __x->_M_parent;


    
//令__y完全顶替__x的地位
    if (__x == __root)                          //__x为根节点
      __root = __y;
    
else if (__x == __x->_M_parent->_M_right)    //__x为其父节点的左子节点
      __x->_M_parent->_M_right = __y;
    
else
      __x
->_M_parent->_M_left = __y;             //__x为其父节点的右子节点
    __y->_M_right = __x;
    __x
->_M_parent = __y;
  }

综观__rb_tree_rebalance的实现代码,我们能够清晰的看到,为了使树形平衡,有时候只许改变节点颜色,有时候需要做单旋转,有时候需要做双旋转(两次单旋转),有时候需要左旋,有时候需要右旋。
所以,rb_tree的rebalance最多只需要两次rotate即可达到目的。

以上分析也印证了一个道理:源码面前,了无秘密。

参考资料:《stl 源码剖析》  侯捷 著
posted on 2007-03-31 20:07  天下大事 必作于细  阅读(1238)  评论(0编辑  收藏  举报