现在的位置: 首页 > 综合 > 正文

算法导论小结(7)-红黑树

2013年10月03日 ⁄ 综合 ⁄ 共 3497字 ⁄ 字号 评论关闭

By:             潘云登

Date:          2009-7-21

Email:         intrepyd@gmail.com

Homepage: http://blog.csdn.net/intrepyd

Copyright: 该文章版权由潘云登所有。可在非商业目的下任意传播和复制。

对于商业目的下对本文的任何行为需经作者同意。


 

写在前面

1.          本文内容对应《算法导论》(2)》第13章。

2.          主要介绍了使二叉查找树保持平衡的红黑树,以及保持红黑树性质的旋转、插入和删除操作。其中,插入和删除操作较为复杂,画图分析每一种情况将有助于理解。

3.          希望本文对您有所帮助,也欢迎您给我提意见和建议。

4.          本文包含以下内容:

²         红黑树的性质

²         旋转操作

²         插入操作

²         删除操作

²         附二叉查找树和红黑树生成的树结构

 


红黑树的性质

 

 

二叉查找树出现不平衡,树的高度较高时,二叉查找树所支持的动态集合操作的性能可能不比用链表好。红黑树(red-black tree)是许多平衡的查找树中的一种。它保持二叉查找树性质,同时在每个结点上增加一个存储位表示结点的颜色。通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其它路径长出两倍,因而是接近平衡的。可以证明,一棵有n个结点的红黑树的高度至多为2 lg(n+1)

    一棵二叉查找树如果满足下面的红黑性质,则为一棵红黑树:1)每个结点或是红的,或是黑的;2)根结点是黑的;3)每个叶结点(NULL)是黑的;4)如果一个结点是红的,则它的两个儿子都是黑的;5)对每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑结点。这种黑结点的个数称为该结点的黑高度bh。

        这里,叶结点(NULL)实际上是之前介绍的树结构的叶结点的子结点,该子结点指针通常赋值为NULL。为了方便处理边界条件,可以采用一个哨兵nil统一表示此类NULL结点,包括树根结点的父结点。哨兵nil的颜色为黑色。

typedef struct node

{

    int key;

    int color;

    struct node *left;

    struct node *right;

    struct node *parent;

} rbt_node;


旋转操作

红黑树的查询操作与二叉查找树相同,如关键字查询、最大关键字结点、最小关键字结点、前趋结点和后继结点等。但是,二叉查找树的插入和删除操作可能破坏红黑树性质。为保持红黑树性质,就要改变树中某些结点的颜色及指针结构。指针结构的修改是通过旋转操作来完成的。旋转操作是一种能保持二叉查找树性质的查找树局部操作,分为左旋和右旋。当在某个结点x上做左旋操作时,x为子树的根,假设它的右子结点y不是nil。在左旋之后,y成为子树新的根,x成为y的左子结点,而y的左子结点成为x的右子结点。可以看到,左旋是以某个结点到其右子结点之间的链为支轴进行。

void left_rotate(rbt_node **root, rbt_node *x_node)

{

    rbt_node *y_node=&nil;

 

    y_node = x_node->right;

    x_node->right = y_node->left;                /*y的左子树作为x的右子树*/

    if(y_node->left != &nil)

                  y_node->left->parent = x_node;

    y_node->parent = x_node->parent;        /*链接x的父结点和y*/

    if(x_node->parent == &nil)

                  *root = y_node;

    else if(x_node == x_node->parent->left)

                  x_node->parent->left = y_node;

    else

                  x_node->parent->right = y_node;

    y_node->left = x_node;                           /*使x成为y的左子结点*/

    x_node->parent = y_node;

}

    右旋与左旋类似。它以某个结点到其左子结点之间的链为支轴进行。当在某个结点x上做右旋操作时,x为子树的根,假设它的左子结点y不是nil。在右旋之后,y成为子树新的根,x成为y的右子结点,而y的右子结点成为x的左子结点。

void right_rotate(rbt_node **root, rbt_node *x_node)

{

    rbt_node *y_node=&nil;

 

    y_node = x_node->left;

    x_node->left = y_node->right;  /*y的右子树作为x的左子树*/

    if(y_node->right != &nil)

                  y_node->right->parent = x_node;

    y_node->parent = x_node->parent;  /*链接x的父结点和y*/

    if(x_node->parent == &nil)

                  *root = y_node;

    else if(x_node == x_node->parent->left)

                  x_node->parent->left = y_node;

    else

                  x_node->parent->right = y_node;

    y_node->right = x_node;  /*使x成为y的右子结点*/

    x_node->parent = y_node;

}


插入操作

红黑树的插入操作以二叉查找树的插入操作为基础,加上一个辅助过程,用以对结点进行重新着色并旋转,从而保持红黑树性质。首先,新插入的结点z总是被着上红色。插入操作只可能破坏红黑树的性质2)或性质4),并且只会有一个性质被破坏。如果违反性质2),则发生的原因是z是根而且是红的。如果违反性质4),则原因是zz.parent都是红的。第一种情形,只要将z重新着上黑色。第二种情形,需要分六种情况讨论。前三种与后三种相互对称,区别在于z的父结点是其祖父结点的左子结点还是右子结点。情况1)与情况23)的区别在于z的父结点的兄弟结点(叔叔)的颜色有所不同。所有三种情况中,z的祖父结点总是黑的,这由性质4)保证,因为z的父结点是红的。

    情况1z的叔叔y为右子结点,是红的。这时,z为当前节点,z的父结点z.parent和叔叔y都是红的,z的祖父是黑的。可以将z的父亲和叔叔都着上黑色以解决zz.parent都是红色的问题,将z的祖父着上红色以保持性质5)。然后,把z的祖父当作当前结点,再次检查z的父结点是否为红的。换句话说,上述循环的终止条件是z的父结点为黑色。这时,z为红色,如果z的父亲为nil(黑色),即z为树根,性质2)被违反,只要将z着上黑色便可加以修正。新插入结点为树根的情况与此相同。

    情况2z的叔叔y为右子结点,是黑的,而且z是右子结点。将z设为z的父结点(z上升一层),然后对z做一次左旋操作(z下降一层),将情况2)转变为情况3)。这时,仍然只有性质4)被破坏。由于z的层数不变,z的祖父的身份保持不变。

    情况3z的叔叔y为右子结点,是黑的,而且z是左子结点。这时,z的左右子树、z的父亲的右子树,以及z的祖父的右子树(以z的叔叔为子树的根),它们的黑高度相同,性质5)成立,只有性质4)在zz的父结点之间被破坏。通过将z的父亲着成黑色,将z的祖父着成红色,并且对z的祖父执行一次右旋操作,可以修正性质4)。所有红黑树性质得以保持,循环终止条件(z的父结点为黑的)成立。由于z原先的祖父现在成为z父结点的右子结点,树的高度减小1,这正是红黑树保持平衡的关键所在。

    情况4z的叔叔y为左子结点,是红的。与情况1)相同,将z的父亲和叔叔都着上黑色以解决zz.parent都是红色的问题,将z的祖父着上红色以保持性质5)。然后,把z的祖父当作当前结点,再次检查z的父结点是否为红的。

    情况5z的叔叔y

抱歉!评论已关闭.