算法之红黑树简单理解和定义

本文深入解析红黑树的原理及实现细节,涵盖插入与删除操作的调整策略,以确保树的自平衡特性。
红黑树(Red Black Tree) 是一种自平衡二叉查找树,是在 计算机科学中用到的一种 数据结构,典型的用途是实现 关联数组
它是在1972年由Rudolf Bayer发明的,当时被称为平衡二叉B树(symmetric binary B-trees)。后来,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改为如今的“红黑树”。
红黑树和AVL树类似,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。
它虽然是复杂的,但它的最坏情况运行时间也是非常良好的,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除,这里的n 是树中元素的数目。
红黑树是每个节点都带有颜色属性的二叉查找树,颜色或红色或黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:
性质1. 节点是红色或黑色。
性质2. 根节点是黑色。
性质3 每个叶节点(NIL节点,空节点)是黑色的。
性质4 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
性质5. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
这些约束强制了红黑树的关键性质: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这个树大致上是平衡的。因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上的理论上限允许红黑树在最坏情况下都是高效的,而不同于普通的二叉查找树。
要知道为什么这些特性确保了这个结果,注意到性质4导致了路径不能有两个毗连的红色节点就足够了。最短的可能路径都是黑色节点,最长的可能路径有交替的红色和黑色节点。因为根据性质5所有最长的路径都有相同数目的黑色节点,这就表明了没有路径能多于任何其他路径的两倍长。
在很多树 数据结构的表示中,一个节点有可能只有一个子节点,而 叶子节点不包含数据。用这种范例表示红黑树是可能的,但是这会改变一些属性并使算法复杂。为此,本文中我们使用 "nil 叶子" 或"空(null)叶子",如上图所示,它不包含数据而只充当树在此结束的指示。这些节点在绘图中经常被省略,导致了这些树好象同上述原则相矛盾,而实际上不是这样。与此有关的结论是所有节点都有两个子节点,尽管其中的一个或两个可能是空叶子。
红黑树和AVL树一样都对插入时间、删除时间和查找时间提供了最好可能的最坏情况担保。这不只是使它们在时间敏感的应用如即时应用(real time application)中有价值,而且使它们有在提供最坏情况担保的其他 数据结构中作为建造板块的价值;例如,在计算几何中使用的很多数据结构都可以基于红黑树。
红黑树在 函数编程中也特别有用,在这里它们是最常用的持久 数据结构之一,它们用来构造 关联数组和集合,在突变之后它们能保持为以前的版本。除了O(log n)的时间之外,红黑树的持久版本对每次插入或删除需要O(log n)的空间。

  因为一颗空的红黑树就是一颗满足所有性质的红黑树,所以构造出一颗红黑树是简单的。重要的是如何在执行可能会破坏红黑树的的操作:插入和删除。如何红黑树继续保持这些性质。

  下面就针对插入和删除时如何保持红黑树的性质进行具体的解释和实现。

  插入和删除都要用到的基本操作是旋转。(旋转是个简单而又经典的操作!)

插入:

  插入的基本操作和二叉查找树的插入操作类似(如果不是很清楚可以到算法导论的151页学习一下,或者到我的上一篇博客看看代码),再对每个新插入的节点的color属性都赋值为红色。为什么不是黑色呢?很简单,如果是赋值为黑色,则每次插入就一定会破坏红黑树的性质。可是如果赋值为红色就不一定啦!可是,红黑树的性质依然可能被破坏,所以我们可以再每次的插入操作之后执行insertRB_fixup操作,来检查红黑树的性质是否被破坏,如果被破坏了,就通过对部分节点的调整来恢复红黑树的性质。

调整的可能情况有:

  如过插入的节点是根节点。此时因为我们每次插入的节点都是红色,会破坏性质2)。这是可以简单的将根节点的颜色改为黑色来恢复红黑树的性质。

  如果插入的节点不是根节点,且这个节点的父节点是黑节点。这时没有破坏任何性质。不需要调整。

  如果插入的节点不是根节点,且这个节点的父节点是红节点。这时性质4)被破坏。这时可以再细分为三种情况(用z表示插入的节点,且设z为左孩子):

  ①z的叔叔y是红色的。

  ②z的叔叔y是黑色的,而且z是右孩子

  ③z的叔叔y是黑色的,而且z是左孩子

  对于①因为z的父节点和叔叔节点都是红色的,则z的祖父节点是存在的且是黑色的。可以讲z的父节点和叔叔节点都有红色改为黑色,再将z的祖父节点有黑色改为红色(这样就不会破坏性质5)),现在z的父亲节点是黑色的了,所以对z来说没有破坏红黑是的性质。不过z的祖父被改为了红色,则又有可能破坏性质4)。同样的问题向上转移了。如果还是情况①,则继续向上转移。直到不再是情况①,最终的结果又四种可能:㈠变为情况②。㈡变为情况③(z指示的节点需要变化一下)。㈢在节点上移到根节点的子节点,因为根是黑的,所以问题结束。㈣z上移到根节点,这是需要将根节点重新改为黑色。

  对于②,只需对z的父节点左旋一下,即可转换为情况③。

  对于③,可以将z的颜色父节点改为黑色,再将z的祖父节点改为红色,之后再对z的祖父节点右旋转一下即可恢复性质4)。

 

删除:(首先是类似二叉树的删除,然后调整。二叉树的任意节点删除会转换为只对有nil子节点的节点的删除,具体参考二叉查找树的删除)

1.如果删除的节点是红色的,则节点删除后,红黑树的性质没有被破坏。
2.如果删除的节点y是黑色的,2)、4)性质可能被破坏。在y被删除之前,y的孩子只有两种可能的情况:①有唯一的一个非nil的子节点。②没有非nil的子节点。
用x表示删除y节点后,代替y位置的节点。可分两种情况:

Ⅰ.x为红色,则是在①的情况下,即y有一个唯一的孩子x,且x为红节点。此时只需将x的颜色由红色改为黑色,即可恢复红黑树的性质。
Ⅱ.x为黑色,又如果x是根节点,则红黑树的性质没有被破坏。如果x不是根节点,则x的父节点一定存在。又因为x是黑节点,所以此时x的父节点一定还存在另一个非nil的节点,
否则违反性质5)。
对x是黑节点,且x的父节点都存在的情况,可以再分成四类,不是一般性的,可以先假设x是父节点的left。
㈠:x的兄弟w是红色的(因为w是红色的,所以x和w的父节点是黑色的)
㈡:x的兄弟w是黑色的,而且w的两个孩子都是黑的
㈢:x的兄弟w是黑色的,而且w的左孩子是红色的,右孩子是黑色的。
㈣:x的兄弟w是黑色的,而且w色右孩子是红色的

现在对删除红黑树节点可能损坏二叉树的性质5)的解决方法进行分析。如果删除了一个黑节点且破坏了性质5),即造成了包含x节点的路径比其他路径少了一个黑的节点。解决方法
可以归纳为两种:①将包含x的节点的路径的黑节点都增加一。②将其他不包含x的路径的黑节点数都减少一。通过这两种解救方法就可以使黑节点数在此平衡。
解决的算法见算法导论的173业,当然也可以看下面我的代码。这个算法的主体部分是对上面的㈠、㈡、㈢、㈣情况的解决,其他的情况都很容易解决。

对于㈠,通过一次旋转,并对两个节点的颜色交换,可以转换为情况㈡或㈢或㈣。
对于㈢,通过一次旋转,并对两个节点的颜色交换,可以转换为请款㈣。
对于㈣,通过一次旋转,并对两个节点的颜色交换,同时再把x的兄弟节点的右子节点的颜色由红色变为黑色,就可以恢复红黑树的性质5)。由颜色的变化就可以发现这儿额外的将一个红节点改为了黑节点。所以属于通过方法①,来恢复红黑树的性质的。其实如果忽略每个节点的数据,只关心每个节点的颜色,这个方法可以简单的理解为从x的父节点的另一颗子树哪儿找到一颗红的节点,将其变为黑色,并插入x为根的那颗子树。但是,为了不破坏红黑树的数据性质,所以通过颜色的交换和节点的旋转来实现。

对于㈡,先将x的兄弟节点的颜色改为红色,这样包含x的兄弟节点的所有路径的黑节点数也都减少了一个,所以现在问题变为包含x的父亲的节点的所有路径的黑节点数都少了一个。这样可以看成x就向上移了一层。然后再判断新的x属于那种情况,如果是㈠、㈢、㈣,则问题解决,如果还是㈡,则继续上移。所以㈡的结束有两种可能,一是在某次上移之后变为了情况㈠、㈢、㈣后解决,二是,一直上移到根节点后结束。这时,所有不包括x节点的路径的黑节点的数目都减少了1,红黑树的性质5)得以恢复。这种解决方法属于解决方法②。

 

写了这么多,全是纯粹的文字。如果不好理解配合和算法导论一起看。(估计以后我自己看的时候,也需要这样。。。囧)

不多说了,贴代码:

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define MAXSIZE 1000
typedef int ElemType;
#define RED    0
#define BLACK    1
typedef struct RBTNode
{
    char    color;
    ElemType    data;
    struct RBTNode *    p;
    struct RBTNode *    left;
    struct RBTNode *    right;
}RBTNode, * PRBTNode;

typedef struct RBTree
{
    PRBTNode    root;
    PRBTNode    nil;        //统一的空节点,该节点是黑的
}RBTree, * PRBTree;

int leftRotate (PRBTree tree, PRBTNode t);
int rightRotate (PRBTree tree, PRBTNode t);
PRBTNode insertRB (PRBTree tree, ElemType d);
int insertRB_fixup (PRBTree tree, PRBTNode t);
int createRBTree (PRBTree tree, ElemType d[], int n);
int initRB (PRBTree tree);
PRBTNode maximum (PRBTree tree, PRBTNode t);
PRBTNode minimum (PRBTree tree, PRBTNode t);
PRBTNode next (PRBTree tree, PRBTNode t);
PRBTNode precursor (PRBTree tree, PRBTNode t);
int walkNext (PRBTree tree);
int inOrderWalk (PRBTree tree, PRBTNode t);
int deleteRB_fixup (PRBTree tree, PRBTNode c);
PRBTNode deleteRB (PRBTree tree, PRBTNode t);
int main ()
{
    PRBTNode p;
    int d[MAXSIZE];
    int n = 0;
    int i;
    RBTree tree;
    initRB(&tree);
    /*
    insertRB(&tree, 11);
    insertRB(&tree, 2);
    insertRB(&tree, 14);
    insertRB(&tree, 1);
    insertRB(&tree, 7);
    insertRB(&tree, 15);
    insertRB(&tree, 5);
    insertRB(&tree, 8);
    insertRB(&tree, 4);
    */
    p=    insertRB(&tree, 26);
    insertRB(&tree, 17);
    insertRB(&tree, 41);
    insertRB(&tree, 14);
    insertRB(&tree, 21);
    insertRB(&tree, 30);
    insertRB(&tree, 47);
    insertRB(&tree, 10);
    insertRB(&tree, 16);
    insertRB(&tree, 19);
    insertRB(&tree, 23);
    insertRB(&tree, 28);
    insertRB(&tree, 38);
    insertRB(&tree, 7);
    insertRB(&tree, 12);
    insertRB(&tree, 15);
    insertRB(&tree, 20);
    insertRB(&tree, 3);
    insertRB(&tree, 35);
    insertRB(&tree, 39);
     

    srand(time(NULL));

    /*
    puts("请输入数据的个数:");
    scanf("%d",&n);
    printf("随机生成的%d个数据是:\n",n);
    for (i = 0; i < n; i++)
    {
        d[i] = rand()%1000;
        printf("%d  ",d[i]);
    }
    puts("");
    puts("建树开始");
    createRBTree(&tree, d, n);
    */

    inOrderWalk(&tree,tree.root);
    puts("");
    printf("根是%d \n",tree.root->data);
    
    printf("删除%d后:",p->data);
    deleteRB(&tree, p);
    
    inOrderWalk(&tree,tree.root);
    puts("");
    printf("根是%d \n",tree.root->data);
    return 0;
}
PRBTNode insertRB (PRBTree tree, ElemType d)
{//插入元素
//!!!记得插入的元素的初始化,p指向为父母节点,left和right赋值为NULL。
    PRBTNode t = NULL;
    PRBTNode p = NULL;
    int flag = 0;        //用来表示插入在左边的树还是右边的树
    t = tree->root;
    
    //插入的节点是root,并做相应的初始化
    if (tree->root == tree->nil)
    {
        tree->root = (PRBTNode)malloc(sizeof(RBTNode));
        tree->root->data = d;
        tree->root->color = BLACK;
        tree->root->p = tree->root->left =tree->root->right = tree->nil;
        
        return tree->root;
    }

    while (t != tree->nil)
    {
        p = t;
        if (d < t->data)
        {
            flag = 0;    
            t = t->left;
        }
        else 
        {
            if (d > t->data)
            {
                flag = 1; 
                t = t->right;
            }
            else
            {
                if ( (flag=rand()%2) == 0)
                    t = t->left;
                else
                    t = t->right;
            }
        }
    }//while

    //将t指向带插入节点的地址,并初始化
    t = (PRBTNode)malloc(sizeof(RBTNode));
    t->data = d;
    t->color = RED;
    t->p = p;
    t->left = t->right = tree->nil;
    
    if (!flag)
        p->left = t;
    else
        p->right = t;

    insertRB_fixup(tree, t);
    return t;
}

int insertRB_fixup (PRBTree tree, PRBTNode t)
{//插入的节点可能破坏红黑树的性质。该函数检测插入的节点是否破坏了红黑树的性质。如果破坏了,就对树进行调整,使其满足红黑树的性质
    while (t->p->color == RED)    //只有插入节点的父亲是红色的才会破坏红黑树的性质(4.如果一个结点是红的,那么它的俩个儿子都是黑的)
    {
        if (t->p->p->left == t->p)    //插入节点的父节点本身是left
        {
            if (t->p->p->right->color == RED)            //case 1
            {                                    
                t = t->p->p;
                t->left->color = t->right->color = BLACK;
                t->color = RED;
            }
            else
            {
                if (t->p->right == t)            //case 2
                {//将case 2转换为了case 3        
                    t = t->p;        //这步赋值是为了在转换为case 3时,t指向的是下面的红节点,和case 3的情况相一致
                    leftRotate(tree, t);
                }
                //case 3
                t->p->color = BLACK;
                t->p->p->color = RED;
                rightRotate(tree, t->p->p);
            }
        }//if
        else    //插入节点的父节点本身是right
        {
            if (t->p->p->left->color == RED)            //case 1
            {                                    
                t = t->p->p;
                t->left->color = t->right->color = BLACK;
                t->color = RED;
            }
            else
            {
                if (t->p->left == t)            //case 2
                {//将case 2转换为了case 3        
                    t = t->p;        //这步赋值是为了在转换为case 3时,t指向的是下面的红节点,和case 3的情况相一致
                    rightRotate(tree, t);
                }
                //case 3
                t->p->color = BLACK;
                t->p->p->color = RED;
                leftRotate(tree, t->p->p);
            }
        }//else
    }//while
    tree->root->color = BLACK;
    return 0;
}
int leftRotate (PRBTree tree, PRBTNode t)
{
    PRBTNode c;        //左旋,c指向t的right
    c = t->right;
    if (t->right == tree->nil) //左旋,t的right不能为空
        return 1;

    //这个if-else用于将t的父亲节点的left或right点指向c,如果t的父节点为不存在,则树的root指向c
    if (t->p != tree->nil)        //判断t是否为root
    {
        if (t->p->left == t)    //判断t是t的父节点的left还是right
            t->p->left = c;
        else
            t->p->right = c;
    }
    else
        tree->root = c;

    c->p = t->p;    //更新c的父节点

    t->right = c->left;
    if (c->left != tree->nil)
        c->left->p = t;
    c->left = t;
    t->p = c;
    return 0;
}
int rightRotate (PRBTree tree, PRBTNode t)
{
    PRBTNode c;        //右旋,c指向t的left
    c = t->left;
    if (t->left == tree->nil) //右旋,t的left不能为空
        return 1;

    //这个if-else用于将t的父亲节点的left或right点指向c,如果t的父节点为不存在,则树的root指向c
    if (t->p != tree->nil)        //判断t是否为root
    {
        if (t->p->left == t)    //判断t是t的父节点的left还是right
            t->p->left = c;
        else
            t->p->right = c;
    }
    else
        tree->root = c;

    c->p = t->p;    //更新c的父节点

    t->left = c->right;
    if (c->right != tree->nil)
        c->right->p = t;
    c->right = t;
    t->p = c;
    return 0;
}
int createRBTree (PRBTree tree, ElemType d[], int n)
{//用元素的插入建树
    int index = -1; 
    int tmp = -1;

    srand(time(NULL));
    
    while (n--)
    {
        index =(int) rand()%(n+1);//此时共有n+1个数据
        tmp = d[index];
        d[index] = d[n];
        d[n] = tmp;
        insertRB(tree, d[n]);
        printf("插入%d\t",d[n]);
    }
    puts("");
    return 0;
}//createRBTree

int initRB (PRBTree tree)
{//红黑树的初始化
    if (tree == NULL)
        return 0;
    tree->nil = (PRBTNode)malloc(sizeof(RBTNode));
    tree->nil->color = BLACK;
    tree->root = tree->nil;
    return 0;
}//initRB

PRBTNode minimum (PRBTree tree, PRBTNode t)
{//返回最小值,如果t是NULL返回NULL

    if (t == tree->nil)
        return NULL;
    while (t->left != tree->nil)
        t = t->left;
    return t;
}//minimum

PRBTNode maximum (PRBTree tree, PRBTNode t)
{//返回最大值,如果t是NULL返回NULL
    if (t == tree->nil)
        return NULL;
    while (t->right != tree->nil)
        t = t->right;
    return t;
}//maximum

PRBTNode next (PRBTree tree, PRBTNode t)
{//给出t的后继的节点。如果没有后继,就返回NULL
    PRBTNode p;        //指示父节点
    if (t->right == tree->nil)
    {
        p = t->p;
        while (p != tree->nil && p->right == t)
        {
            t = p;
            p = t->p;
        }
        return p;    //如果是最后一个元素,p的值为NULL
    }
    else
        return minimum(tree, t->right);
}//next

PRBTNode precursor (PRBTree tree, PRBTNode t)
{//返回节点t前驱,如果没有前驱,就返回NULL
    PRBTNode p;
    if (t->left == tree->nil)
    {
        p = t->p;
        while (p != tree->nil && p->left == t)
        {
            t = p;
            p = t->p;
        }
        return p;
    }
    else
        return maximum(tree, t->left);
}//precusor

int walkNext (PRBTree tree)
{//遍历二叉搜索树。先找到最小的元素,再通过用next()求后继来遍历树
    PRBTNode t;
    t = minimum(tree,tree->root);
    while (t != tree->nil)
    {
        printf("%d ",t->data);
        if (t->color == BLACK)
            printf("B\t");
        else
            printf("R\t");
        t = next(tree,t);
    }
    return 0;
}//walkNext

PRBTNode deleteRB (PRBTree tree, PRBTNode t)
{//删除数据。要求给处数据节点的指针
    PRBTNode c = NULL;        //c指向要取代被删除节点的子节点
    PRBTNode d = NULL;
    ElemType tmp;
    if (t == tree->nil)
        return NULL;

    //d指向真正要删除的元素的下标。如果t的left和right都有值,则转化为删除t的后继节点,并把后继节点的内容复制给t指向的节点。
    //而其他情况则直接删除t指向的节点
    if (t->left != tree->nil && t->right != tree->nil)
    {
        d = next(tree, t);
        //因为实际操作要删除的是d指向的节点,所以先交换data
        tmp = d->data;
        d->data = t->data;
        t->data = tmp;
    }
    else
        d = t;

    //确定c的指向
    if (d->left == tree->nil)
        c = d->right;
    else
        c = d->left;

    //将c的父亲指针设为d的父亲指针,c不会为空(因为存在nil节点)
    c->p = d->p;
    if (d->p != tree->nil)
    {
        if (d->p->left == d)
            d->p->left = c;
        else
            d->p->right = c;
    }
    else
        tree->root = c;

    if (d->color == BLACK)
        deleteRB_fixup(tree, c);
    return d;
}//deleteRB

int deleteRB_fixup (PRBTree tree, PRBTNode c)
{
    PRBTNode b;        //兄弟节点

    while (c != tree->root && c->color == BLACK)
    {
        if (c == c->p->left)
        {
            b = c->p->right;

            if (b->color == RED) //case 1  
            {//b节点是红的,可以说明c和b的父亲节点是黑的。通过以下的操作可以吧case 1转换为case 2,3,4中的一个
                b->color = BLACK;
                c->p->color = RED;
                leftRotate(tree, c->p);
                b = c->p->right;    //新的兄弟节点,这个节点一定是黑色的。这个节点之前是红色节点的儿子
            }
            if (b->right->color == BLACK && b->left->color == BLACK) //case 2
            {
                b->color = RED;        //将c的父节点的另一颗子树黑节点减少1
                c = c->p;            //将c上移。上移之后,c的黑高度相同了(因为另一颗子树的根节点有黑边为红)
            }
            else    //case 3或case 4
            {
                if (b->right->color == BLACK)        //case 3    通过以下操作将case 3 转化为case 4
                {
                    b->color = RED;
                    b->left->color = BLACK;
                    rightRotate(tree, b);
                    b = c->p->right;
                }
                //case 4
                //通过下面的操作,红黑树的性质恢复
                b->color = b->p->color;
                b->p->color = BLACK;
                b->right->color = BLACK;
                leftRotate(tree, c->p);
                c = tree->root;        //红黑树性质恢复,结束循环。不用break,是因为while结束后还要执行c->color = BLACK;
            }
        }//if (c == c->p->left)
        else
        {
            b = c->p->left;
            if (b->color == RED) //case 1  
            {//b节点是红的,可以说明c和b的父亲节点是黑的。通过以下的操作可以吧case 1转换为case 2,3,4中的一个
                b->color = BLACK;
                c->p->color = RED;
                rightRotate(tree, c->p);
                b = c->p->left;    //新的兄弟节点,这个节点一定是黑色的。这个节点之前是红色节点的儿子
            }
            if (b->right->color == BLACK && b->left->color == BLACK) //case 2
            {
                b->color = RED;        //将c的父节点的另一颗子树黑节点减少1
                c = c->p;            //将c上移。上移之后,c的黑高度相同了(因为另一颗子树的根节点有黑边为红)
            }
            else    //case 3或case 4
            {
                if (b->left->color == BLACK)        //case 3    通过以下操作将case 3 转化为case 4
                {
                    b->color = RED;
                    b->right->color = BLACK;
                    leftRotate(tree, b);
                    b = c->p->left;
                }
                //case 4
                //通过下面的操作,红黑树的性质恢复
                b->color = b->p->color;
                b->p->color = BLACK;
                b->left->color = BLACK;
                rightRotate(tree, c->p);
                c = tree->root;        //红黑树性质恢复,结束循环。不用break,是因为while结束后还要执行c->color = BLACK;
            }
        }//else
        
    }
    c->color = BLACK;
    return 0;
}//deleteRB_fixup

int inOrderWalk (PRBTree tree, PRBTNode t)
{//中序遍历
    if (t == tree->nil)
        return 0;
    putchar('(');
    inOrderWalk (tree, t->left);
    putchar(')');

    printf(" %d ",t->data);
    if (t->color == BLACK)
        printf("B");
    else
        printf("R");

    putchar('(');
    inOrderWalk (tree, t->right);
    putchar(')');
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值