这两天继续研究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_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;
}
//新节点必定是红节点,如果插入处的父节点也是红节点,就违反了红黑树规则,此时必须
//对红黑树进行旋转(以及改变颜色)
//此函数为左旋转函数
//第一个参数是左旋点
//第二个参数是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 源码剖析》 侯捷 著