程序员

C语言——红黑树

2018-04-29  本文已影响0人  薛定谔与猫的故事

性质
红黑树的节点包括5个属性:color、key、left、right、parent。如果一个节点没有子节点或者父节点,则该节点响应的指针属性的值为nil,把这些nil视为指向二叉搜索树的叶节点的指针,而把待关键字的节点视为树的内部节点。

红黑树必须满足下列五个条件:

为了便于处理红黑树的边界条件,可以使用一个哨兵来代表NIL,哨兵的结构与红黑树的节点结构相同,color为黑色,其他属性任意设置。

红黑树

红黑树是一种特殊的二叉搜索树,它的插入和删除更加复杂。因为红黑树必须满足上面的五个条件,插入与删除有可能会破坏上面的性质,所以在插入和删除之后需要调整红黑树,改变树中某些节点的颜色以及指针结构。

颜色容易改变,而指针结构不容易改变,这时我们使用旋转来调整指针结构。
旋转分为左旋和右旋,通常是以关键字小的节点为轴心,轴心到关键字大的节点的路径为轴旋转。(希腊字母表示的是任意子树)x,y不等于nil。

旋转

左旋代码:(右旋代码读者可以自己尝试写写)

左旋示例
void left_rotate(RB_Tree *T,TNode *x){
    TNode *y = x->right;
    x->right = y->left;
    if(y->left!=T->nil){
        y->left->parent = x;
    }

    y->parent = x->parent;
    if(x->parent == T->nil){
        T->root = y;
    }else if(x==x->parent->left){
        x->parent->left = y;
    }else{
        x->parent->right = y;
    }

    y->left = x;
    x->parent = y;
}

插入
插入的节点的颜色为红色(因为如果插入的是黑色,那么会改变黑高,违背性质5)

调整
如果插入的父节点是黑色,显然是不用调整,所以我们只需要考虑插入位置的父节点的颜色是红色的情况。
插入情况导致冲突情况有如下三种,如方框所示,z节点是插入位置(有人可能会问不是应该插在叶节点(nil)位置?这里主要是为了说明三种情况的转换,我们可以吧方框下面部分当成nil,这里只考虑添加在爷节点的左子树的情况,右子树的情况与左子树完成相反,但对称)

情况一:插入节点的父节点和叔节点均为红色。
调整:此时,我们只需把父节点和叔节点的颜色改为黑色,父节点的父节点(简称爷节点【捂脸】)的颜色改为红色(不改变性质5)。(显然的,如果爷节点的父节点为黑色,则调整完成)

情况一

情况二:插入节点的叔节点为黑色,且插入节点是父节点的右孩子节点。
调整:以插入点的父节点为轴心左旋(显然,调整未完成)

情况二

情况三:插入节点的叔节点为黑色,且插入节点是父节点的左孩子节点。
调整:以插入点的父节点为轴心,右旋(显然,必然完成)

情况三

对于这三种情况的转换。

void rb_insert_fixup(RB_Tree *T,TNode *z){
    while(z->parent->color == RED){
         //插入位置在爷节点的左子树
        if(z->parent==z->parent->parent->left){
            TNode *y = z->parent->parent->right;
            if(y->color==RED){
                z->parent->color = BLACK;
                z->parent->parent->color = RED;
                y->color = BLACK;
                z=z->parent->parent;
            }else if(z == z->parent->right){
                z = z->parent;
                left_rotate(T,z);
            }else{
                z->parent->color = BLACK;
                z->parent->parent->color = RED;
                right_rotate(T,z->parent->parent);
            }   
        }else{
            TNode *y = z->parent->parent->left;
            if(y->color==RED){
                z->parent->color = BLACK;
                z->parent->parent->color = RED;
                y->color = BLACK;
                z=z->parent->parent;
            }else if(z == z->parent->left){
                z = z->parent;
                right_rotate(T,z);
            }else{
                z->parent->color = BLACK;
                z->parent->parent->color = RED;
                left_rotate(T,z->parent->parent);
            }   
        }
    }
    T->root->color = BLACK;
}

void rb_insert(RB_Tree *T,TNode *z){

    TNode *y = T->nil;
    TNode *x = T->root;
    //找插入位置
    while(x!=T->nil){
        y = x;
        if(z->key<y->key){
            x = x->left;
        }else{
            x = x->right;
        }
    }

    z->parent = y;

    if(y==T->nil){
        T->root = z;
        T->root->parent = T->nil;
    }else if(z->key<y->key){
        y->left = z;
    }else{
        y->right = z;
    }

    z->left = T->nil;
    z->right = T->nil;
    z->color = RED;
    //调整
    rb_insert_fixup(T,z);
}

删除:
删除相对于插入来说更加复杂……

首先设计一个删除调用的子过程(用一棵树代替另一棵树的子树)(u是被替代的子树 )

void rb_translate(RB_Tree *T,TNode *u,TNode *v){
    if(u->parent==T->nil){
        T->root = v;
    }else if(u == u->parent->left){
        u->parent->left = v;
    }else{
        u->parent->right = v;
    }
    v->parent = u->parent;
}

删除节点的三种情况,前面的二叉搜索树已经提到过了,这里主要是讲删除后,红黑树的调整。

我们假设有一个节点y,但被删除的节点z的子节点少于2个时,让y成为z(所谓成为z,即是把z的颜色传递给y,并且保持二叉树性质不变,这一点很重要)。当大于2时,y应当是z的后继节点(即z的右节点或是右节点的最左子代)。y有可能导致红黑树性质的破坏,所以,应该需要记录原本y节点的颜色(y-original-color)。此外,还需要节点x来记录原本y的唯一子节点(x可能为nil)。

删除节点情况

如果原本y颜色是红色,显然是不会改变红黑树性质(y不是根节点);反之,则会。
当y替代了z后,因为z被删除,y替代上去,如果原本y的颜色是红色,那么原本y所在的节点的子树上的黑高不变,而y替代z后,以z为根节点的子树黑高也不变,没有影响到红黑树的性质。所以这里只讨论原本y颜色是黑色情况。

在原本y颜色是黑色前提下:

而如果x的颜色为黑色,那么可能会出现下面的四种情形:(这里只讨论x所在子树为左子树的情况,右子树与左子树对称相反)
黑高:从某个节点出发(不含该节点),到达一个叶节点的任意一条简单路径上的黑色节点(必须是内部节点)个数)

情况一 左旋后

x(B)的兄弟节点(C)为黑色,那么x父节点(A)可黑可红,可分三种情形。

对于x是x父节点的右孩子的情况,是与x是x父节点的左孩子的情况一样,只不过把对应的左改成右,右变为左。
代码实现

//找后继
TNode* tree_min(TNode* R,RB_Tree T){
    while(R->left!=T.nil){
        R = R->left;
    }
    return R;
}
//修复删除节点z后的红黑树性质
void rb_delete_fixup(RB_Tree *T,TNode *x){
    while(x!=T->root && x->color == BLACK){
        //x是父节点的左孩子
        if(x==x->parent->left){
            //w是x的兄弟节点
            TNode *w = x->parent->right;
            //情况一
            if(w->color == RED){
                w->color = BLACK;
                w->parent->color = RED;
                left_rotate(T,x->parent);
                w = x->parent->right;
            }
            //情况二
            if(w->left->color==BLACK && w->right->color == BLACK){
                w->color = BLACK;
                x = x->parent;
            }else if(w->right->color == BLACK){
             //情况三
                w->left->color = BLACK;
                w->color = RED;
                right_rotate(T,w);
                w = x->right;
            }else{
            //情况四
                w->color = x->parent->color;
                x->parent->color = BLACK;
                w->right->color = BLACK;
                left_rotate(T,x->parent);
                x = T->root;
            }   
        }else{
    //x是父节点的右孩子
            TNode *w = x->parent->left;
            if(w->color == RED){
                w->color = BLACK;
                w->parent->color = RED;
                left_rotate(T,x->parent);
                w = x->parent->left;
            }

            if(w->right->color==BLACK && w->left->color == BLACK){
                w->color = BLACK;
                x = x->parent;
            }else if(w->left->color == BLACK){
                w->right->color = BLACK;
                w->color = RED;
                left_rotate(T,w);
                w = x->left;
            }else{
                w->color = x->parent->color;
                x->parent->color = BLACK;
                w->left->color = BLACK;
                right_rotate(T,x->parent);
                x = T->root;
            }
        }
    }
    x->color = BLACK;
}
void rb_delete(RB_Tree *T,TNode* z){
    TNode* y = z;
    TNode* x = T->nil;
    //记录原本y的颜色
    Color y_original_color = y->color;

    if(z->left==T->nil){
        x = z->right;
        rb_translate(T,z,z->right);
    }else if(z->right==T->nil){
        x = z->left;
        rb_translate(T,z,z->left);
    }else{
        y = tree_min(z->right,*T);
        y_original_color = y->color;
        x = y->right;
        if(y->parent == z){
            x->parent = y;
        }else{
            rb_translate(T,y,y->right);
            y->right = z->right;
            y->right->parent = y;
        }
        rb_translate(T,z,y);
        y->left = z->left;
        y->left->parent = y;
        y->color = z->color;
    }
    if(y_original_color==BLACK){
        rb_delete_fixup(T,x);
    }
}

测试代码:

//中序遍历
void rb_in_order_traverse(RB_Tree T,TNode* R){
    if(R!=T.nil){
        rb_in_order_traverse(T,R->left);
        printf("%d is red:%d \n",R->key ,R->color);
        rb_in_order_traverse(T,R->right);
    }
}
//由于这里使用的是深度优先遍历,很难体现出树的形状,建议读者用广度优先遍历(可利用队列或者栈实现)
int main(int argc, char const *argv[])
{
    RB_Tree T;
    init_rb_tree(&T);
    int n;
    scanf("%d",&n);
    for (int i = 0; i < n; ++i){
        TNode *z = (TNode*)malloc(sizeof(TNode));
        KeyType key = rand()%100;
        printf("key==%d\n",key );
        z->key = key;
        rb_insert(&T,z);
    }
    rb_in_order_traverse(T,T.root);
    printf("\n");

    rb_delete(&T,tree_min(T.root,T));
    rb_in_order_traverse(T,T.root);
    return 0;
}
上一篇 下一篇

猜你喜欢

热点阅读