数据结构之红黑树的生成、添加以及删除详解(附代码)

一,红黑树的来历

红黑树,首先是一个二叉树,对于二叉树,人们为了提升它的搜索效率,降低时间复杂度,创造出了二叉搜索树,把时间复杂度降低为对数级(LOGn),但是会出现一些极端情况,如下面:

 上面也是一棵二叉搜索树,符合二叉搜索树的性质,但它已经褪变成一个链表了,时间复杂度为O(n),常数级.所以为了解决这个问题,又创造出来平衡二叉树,平衡二叉树我在前面的文章里已经详细介绍过了,时间复杂度为对数级O(logn),但是平衡二叉树在添加和删除元素时,为了保持左右子树的平衡,有大量的旋转操作.比较影响效率.于是,红黑树出场了,它相对平衡二叉树来说,减少了旋转次数,因为它没有严格的左右子树平衡的要求,它只需要做到黑色平衡就可以了,所以旋转次数减少的同时,时间复杂度没有增加,这就是红黑树.

二,红黑树的定义

要了解红黑树,那必须得了解它的五大特性:

1,所有结点非红即黑;(这个就不必细说了)

2,根必须是黑色的;

3,所有叶子结点都是黑色的;(这里的叶子结点是指的NIL空结点,一会我会在图里说明)

4,每个红色结点的子结点必须是黑色的;(从根到每个叶子结点的所有路径上不能出现两个连续的红色结点)

5,从任一结点出发,到其每个叶子结点的路径上,都会经过相同数目的黑色结点(这里就是我上面提到过的黑色平衡

 上图中,根结点5必须是黑色的

2,4,6,8结点必须是黑色的,不然会产生连续的红色结点,违背第4条规则

从根结点5号出发,到任意叶子结点NIL,所经过的黑色结点数量是一样的,都经过了两个黑色结点,从3号出发,到NIL点,都经过了一个黑色结点

所有的NIL点都是黑色的(在红黑树中,大家习惯把NIL结点称为叶子结点

三,红黑树的生成

 1,算法思路

和平衡二叉树一样,我们往一棵空的红黑树当中添加元素,这里要特别注意(规定添加结点时,这个被添加的结点的颜色必须是红色的),如果为空树,直接添加为根结点,因为又有规定,根结点必须为黑色,所以要把添加进去的红色根结点变为黑色根结点.

继续添加,判断树不为空,然后从根结点开始,和添加的结点的值比较大小,如果值比根结点大则指向根结点的右孩子,反之,则是左孩子.然后继续和右孩子或者左孩子比较大小,直到找到空指针为止,然后把这个结点加上去.注意:如果是平衡树,这时候应该是要更新平衡因子,判断树是否失衡,如果失衡就需要调整.但是红黑树没有平衡因子,此时,它只判断加入该结点后,该树是否还符合红黑树的五大特性

因为加入的结点为红色的,所以不会影响黑色平衡,只对第4条:不能出现连续的红结点有影响,所以这时就要判断,加入的结点的父亲结点是不是红色,如果父亲结点为黑色,那就不需要调整,如果父亲结点为红色,那就要调整(具体怎么调整我下面马上会讲),直到调整到该树符合五大特性为止.然后继续添加结点,直到结束.

2,调整

我总结了一下,一共有以下6种情况需要调整:

上面4种情况,也不用强记怎么变的,看过我上一篇平衡二叉树的,应该都知道 

首先判断添加的结点的父亲结点,是爷爷结点的左孩子还是右孩子,左孩子记L,右孩子记R.然后再判断这个添加的结点是父亲结点的左孩子还是右孩子,左孩子记L,右孩子记R.

在三个结点中(添加的结点,添加的结点的父亲结点,添加的结点的爷爷结点),中间值的点变成黑色结点,做为这棵小树的根结点,另外两个结点按规则分别为左右孩子且都为红色结点,最后一个结点按排序树的规则也可以推断出放的位置.

最后两种:

 添加的结点,在有红色叔叔结点的时候,不需要调整,只需要变色

只有在没有叔叔结点的时候,才需要进行最上面的4种调整

 这里还有一个特别重要的,上面两种变色的类型中,如果添加的结点的爷爷是根结点,那么根结点要变成黑色结点(第二特性),如果不是根结点,而且爷爷的父亲结点也是红色,那么就会产生双红结点,所以,必须以爷爷结点做为添加的结点,递归去判断上层有没有双红节点,直到根结点为止.

四,红黑树的删除操作

红黑树的平衡是黑色平衡,也就是说,如果删除的结点是红色,它是不需要调整的

红黑树的删除有以下三种情况:

1,删除的结点有左右两个孩子 

比如,上面我们要删除结点6,那么我们先找到结点6前驱的最大值,或者后继的最小值,这里我们就拿后继的最小值7,然后把结点7的值赋给结点6,然后再删除结点7就可以了

2,删除的结点只有一个孩子

如上图,结点5和7,都只有一个孩子,这种情况,直接删除,然后孩子顶替它的位置,为了保证平衡,孩子的颜色要变为黑色

上图:如果结点5为黑色,那么结点4必为红色 

 3,删除的结点,是叶子结点,没有孩子

这种情况也是直接删除,不过如果这个结点是黑色的话,是需要调整的,调整的方法和这个要删除的黑色结点的兄弟结点有关,根据它兄弟结点的状态,有以下三种情况:

(前提:兄弟结点必须是黑色,如果是红色,兄弟结点是父亲结点的左子树就以父亲结点为基点LL调整,反之则RR调整,调整完后,兄弟结点就变成了黑色)

1,兄弟结点没有孩子

到这里,我估计有人会说了,如果这里的9结点本来就是黑色,那不是就不平衡了.

是的,如果父亲结点本来为黑色,那么就是父亲结点为基点,再找它的兄弟结点,然后判断兄弟结点的三个情况,继续调整.

2,兄弟结点有一个孩子

如果兄弟是父亲结点的左孩子,兄弟结点有一个左孩子,那么此时就以父亲结点为基点LL调整,如果兄弟结点的孩子是右孩子,那么就LR调整 

 如果兄弟是父亲结点的右孩子,兄弟结点有一个左孩子,那么此时就以父亲结点为基点RL调整,如果兄弟结点的孩子是右孩子,那么就RR调整 

3,兄弟结点有两个孩子

此时,如果兄弟结点是父亲结点的左孩子,那么就LL调整,如果兄弟结点是父亲结点的右孩子,那么就RR调整. 

 具体的代码为:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>

typedef struct Node{
    int data;
    char color;
    struct Node* left;
    struct Node* right;
    struct Node* parent;
}Node,*BRTree;
BRTree currentNode = NULL;
BRTree insertEle(BRTree* node, int m, BRTree parent)
{
    if(!(*node))
    {
        *node = malloc(sizeof(Node));//创建一个红色的结点,并初始化
        currentNode = *node;//保存插入的节点
        (*node)->data = m;
        (*node)->color = 'r';
        (*node)->left = NULL;
        (*node)->right = NULL;
        (*node)->parent = parent;
        if(!parent)(*node)->color = 'b';//如果这个节点是根结点,变为黑色
    }
    else
    {
        if(m < (*node)->data)//递归寻找插入点
        {
            insertEle(&(*node)->left, m, *node);
        }
        else if(m > (*node)->data)
        {
            insertEle(&(*node)->right, m, *node);
        }
        else return currentNode;//返回插入的这个结点
    }
    return currentNode;//返回插入的这个结点
}

void setLL(BRTree* T, BRTree a)
{
    BRTree b = a->left;
    if(a->parent)//如果结点不是根结点
    {
        if(a==a->parent->left)//如果该结点,是它父亲结点的左孩子
        {
            a->parent->left = b;//该结点的父亲结点的左孩子指向b
        }
        else
        {
            a->parent->right = b;//否则,该结点的父亲结点的右孩子指向b
        }
    }
    else
    {
        *T = b;//如果该结点是根结点,那么要把调整后b作为根结点
    }
    a->left = b->right;
    if(b->right)b->right->parent = a;
    b->right = a;
    b->parent = a->parent;
    a->parent = b;



}

void setRR(BRTree* T, BRTree a)
{
    BRTree b = a->right;
    if(a->parent)
    {
        if(a==a->parent->left)
        {
            a->parent->left = b;
        }
        else
        {
            a->parent->right = b;
        }
    }
    else
    {
        *T = b;
    }
    a->right = b->left;
    if(b->left)b->left->parent = a;
    b->left = a;
    b->parent = a->parent;
    a->parent = b;
}

void setLR(BRTree* T, BRTree a)
{
    BRTree b = a->left;
    BRTree c = a->left->right;
    if(a->parent)
    {
        if(a==a->parent->left)
        {
            a->parent->left = c;
        }
        else
        {
            a->parent->right = c;
        }
    }
    else
    {
        *T = c;
    }
    b->right = c->left;
    a->left = c->right;
    if(c->left)c->left->parent = b;
    if(c->right)c->right->parent = a;
    c->left = b;
    c->right = a;
    c->parent = a->parent;
    b->parent = c;
    a->parent = c;


}

void setRL(BRTree* T, BRTree a)
{
    BRTree b = a->right;
    BRTree c = a->right->left;
    if(a->parent)
    {
        if(a==a->parent->left)
        {
            a->parent->left = c;
        }
        else
        {
            a->parent->right = c;
        }
    }
    else
    {
        *T = c;
    }
    b->left = c->right;
    a->right = c->left;
    if(c->right)c->right->parent = b;
    if(c->left)c->left->parent = a;
    c->right = b;
    c->left = a;
    c->parent = a->parent;
    b->parent = c;
    a->parent = c;

}
void adjust(BRTree* T, BRTree node)//碰到双红节点后的调整
{
    int flag = 1;
    do
    {
        flag = 0;
        if(node->parent==NULL)return;
        if(node->parent->color=='r')
        {
            if(node->parent->parent->left==node->parent)//如果爷爷结点的左孩子是父亲结点
            {
                if(node->parent->parent->right && node->parent->parent->right->color=='r')//如果有叔叔结点,且为红色
                {
                    node->parent->color = 'b';//父亲结点变黑色
                    node->parent->parent->right->color = 'b';//叔叔结点变黑色
                    node->parent->parent->color = 'r';//爷爷结点变成红色
                    if(node->parent->parent==*T)node->parent->parent->color = 'b';//如果爷爷结点是根结点,那么此时要变回黑色
                    node = node->parent->parent;//结点指向爷爷结点,再递归调整
                    flag = 1;//需要再次判断,所以打开循环开关
                }
                else
                {
                    if(node->parent->left == node)//该结点是父亲结点的左孩子
                    {
                        node->parent->color = 'b';
                        node->parent->parent->color = 'r';
                        setLL(T, node->parent->parent);
                    }
                    else//该结点是父亲结点的右孩子
                    {
                        node->color = 'b';
                        node->parent->parent->color = 'r';
                        setLR(T, node->parent->parent);
                    }
                }
            }
            else
            {
                if(node->parent->parent->left && node->parent->parent->left->color == 'r')//如果有叔叔结点,且为红色
                {
                    node->parent->color = 'b';
                    node->parent->parent->left->color = 'b';
                    node->parent->parent->color = 'r';
                    if(node->parent->parent==*T)
                    {
                        node->parent->parent->color = 'b';
                    }
                    flag = 1;//需要再次判断,所以打开循环开关
                    node = node->parent->parent;//结点指向爷爷结点,再递归调整


                }
                else
                {
                    if(node->parent->left == node)
                    {
                        node->color = 'b';
                        node->parent->parent->color = 'r';
                        setRL(T, node->parent->parent);
                    }
                    else
                    {
                        node->parent->color = 'b';
                        node->parent->parent->color = 'r';
                        setRR(T, node->parent->parent);
                    }
                }
            }
        }
    }
    while(flag);

}



void swapArr(BRTree a[], BRTree b[],int len)//为了打印红黑树,写的交换数组函数,与红黑树无关
{
    int i;
    BRTree temp;
    for(i = 0; i < len; i++)
    {
        temp = a[i];
        a[i] = b[i];
        b[i] = temp;

    }
}


void printRBTree(BRTree node)//广度遍历该红黑树,并打印出来.与红黑树本身无关,无需了解
{
    BRTree a[16],b[16];
    a[0] = node;//数组a中起始元素个数1个(根结点)
    int nums = 1;//数组a中起始元素个数1个(根结点)
    int flag = 1;//判断数组a中,有没有元素
    int i;
    int blank = 20;
    char str[100] = "";
    int c1 = 10;
    int c2 = 5;
    int c3 = 3;
    int c4 = 2;
    while(flag)
    {
        flag = 0;
        int j = 0;
        blank--;
        printf("%*s",blank--,"");

        for(i = 0; i < nums; i++)
        {
            if(a[i])
            {
                HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
                if(a[i]->color == 'r')
                {
                    SetConsoleTextAttribute(hConsole, 0xC );
                }
                else if(a[i]->color == 'b')
                {
                    SetConsoleTextAttribute(hConsole, 2 );
                }

                printf("%d",a[i]->data);
                SetConsoleTextAttribute(hConsole, 0xF );
                if(nums==2)printf("%*s",c1," ");
                if(nums==4)printf("%*s",c2," ");
                if(nums==8)printf("%*s",c3," ");
                if(nums==16)printf("%*s",c4," ");
                if(a[i]->left)
                {
                    flag = 1;
                    b[j++] = a[i]->left;
                    if(nums==1)strcat(str, "/      ");
                    if(nums==2)strcat(str, "/  ");
                    if(nums==4)strcat(str, "/ ");
                    if(nums==8)strcat(str, "/ ");
                }
                else
                {
                    b[j++] = 0;
                    if(nums==1)strcat(str, "      ");
                    if(nums==2)strcat(str, "    ");
                    if(nums==4)strcat(str, "   ");
                    if(nums==8)strcat(str, "  ");
                }
                if(a[i]->right)
                {
                    flag = 1;
                    b[j++] = a[i]->right;
                    if(nums==1)strcat(str, "\\        ");
                    if(nums==2)strcat(str, "\\        ");
                    if(nums==4)strcat(str, "\\   ");
                    if(nums==8)strcat(str, "\\  ");
                }
                else
                {
                    b[j++] = 0;
                    if(nums==1)strcat(str, "         ");
                    if(nums==2)strcat(str, "         ");
                    if(nums==4)strcat(str, "    ");
                    if(nums==8)strcat(str, "  ");
                }
            }
            else
            {
                if(nums==2)printf("%*s",c1+1," ");
                if(nums==4)printf("%*s",c2+1," ");
                if(nums==8)printf("%*s",c3+1," ");
                if(nums==16)printf("%*s",c4," ");
                b[j++] = 0;
                b[j++] = 0;
                if(nums==1)strcat(str, "            ");
                if(nums==2)strcat(str, "        ");
                if(nums==4)strcat(str, "      ");
                if(nums==8)strcat(str, "    ");

            }

        }
        nums*=2;
        printf("\n");
        blank--;
        printf("%*s",blank--,"");
        printf(str);
        printf("\n");
        memset(str,0, sizeof(str));
        int len = sizeof(a)/sizeof(a[0]);
        swapArr(a,b,len);
    }
}

void CreatRBTree(BRTree* node)
{
    int m=1;
    while(m)
    {
        printf("请输入要添加的结点的值并以0结束:>");
        scanf("%d",&m);
        if(m==0)break;
        BRTree A = insertEle(node, m, *node);//添加元素
        adjust(node, A);//判断并调整
        printRBTree(*node);

    }
}

BRTree Successor(BRTree T, BRTree a)//寻找后继点,即右孩子中的最小值结点
{
    if(a==NULL)
    {
        return NULL;
    }
    else
    {
        BRTree p = a->right;
        while(p->left)
        {
            p = p->left;
        }
        return p;
    }
}

BRTree FindNode(BRTree T, int a)//根据提供的数值,寻找树中对应的结点
{
    while(T)
    {
        if(T->data == a)
        {
            return T;
        }
        else if(T->data > a)
        {
            T = T->left;
        }
        else
        {
            T = T->right;
        }
    }
    printf("没有找到你要删除的数据!!");
    return NULL;
}
//删除节点后的调整
void modify(BRTree *T, BRTree x)
{
    //删除的结点颜色是黑色,才需要调整
    while(x->color=='b')
    {
        if(x==x->parent->left)//如果要删除的结点是父亲结点的左孩子
        {
            BRTree rnode = x->parent->right;//那么兄弟结点为父亲结点的右孩子
            if(rnode->color == 'r')//如果兄弟结点为红色,那么需要调整,是右孩子就RR调整
            {
                rnode->color = 'b';
                x->parent->color = 'r';
                setRR(T,x->parent);
                //找到真正的兄弟结点
                rnode = x->parent->right;//调整完成后,更新兄弟结点
            }
            //情况一:
            if(rnode->left==NULL&&rnode->right==NULL)//兄弟结点没有孩子
            {
                rnode->color = 'r';
                x = x->parent;//焦点指向它的父亲,等待下一轮判断,如果它父亲的颜色为红色,则不会进入循环
            }
            else
            {
                //情况二和三
                if(rnode->right==NULL)
                {
                    rnode->left->color = x->parent->color;
                    x->parent->color = 'b';
                    x->color = 'r';
                    setRL(T, x->parent);
                }
                else
                {
                    rnode->color = x->parent->color;
                    x->parent->color = 'b';
                    rnode->right->color = 'b';
                    x->color = 'r';
                    setRR(T, x->parent);
                }
            }
        }
        else
        {
            BRTree rnode = x->parent->left;
            if(rnode->color == 'r')
            {
                rnode->color = 'b';
                x->parent->color = 'r';
                setLL(T,x->parent);
                //找到真正的兄弟结点
                rnode = x->parent->left;
            }
            if(rnode->left==NULL&&rnode->right==NULL)//兄弟结点没有孩子
            {
                rnode->color = 'r';
                x = x->parent;
            }
            else
            {
                if(rnode->left==NULL)
                {
                    rnode->right->color = x->parent->color;
                    x->parent->color = 'b';
                    x->color = 'r';
                    setLR(T, x->parent);
                }
                else
                {
                    rnode->color = x->parent->color;
                    x->parent->color = 'b';
                    rnode->left->color = 'b';
                    x->color = 'r';
                    setLL(T, x->parent);
                }
            }
        }
    }
    x->color = 'b';
}
//删除结点
void DelNode(BRTree *T, int b)
{
    if(*T==NULL)return;
    BRTree a = FindNode(*T, b);
    if(a->left && a->right)//要删除的结点,既有左孩子,又有右孩子
    {
        BRTree temp = Successor(*T, a);//选择该结点,左子树中的最大值,或者右子树中的最小值.作为替换结点
        a->data = temp->data;//找到替代点,并把值拷备过来
        a = temp;//把删除对像转向替代点
    }
    //要删除的结点,有一个孩子的情况
    BRTree replace = a->left==NULL?a->right:a->left;//这个孩子的地址
    if(replace!=NULL)
    {
        replace->parent = a->parent;
        if(a->parent==NULL)
        {
            *T = replace;
        }
        //用replace替换要删除的结点
        else if(a->parent->left == a)
        {
            a->parent->left = replace;
        }
        else
        {
            a->parent->right = replace;
        }
        if(a->color =='b')
        {
            modify(T, replace);//调整
        }
        //置空,并释放内存
        a->left = a->parent = NULL;
        free(a);
    }
    //replace为空,说明该删除点,没有孩子
    else
    {
        modify(T, a);
        //直接删除该结点,关释放内存
        if(a==a->parent->left)a->parent->left=NULL;
        else a->parent->right = NULL;
        a->left = a->parent = NULL;
        free(a);
    }


}
int main()
{
    int input;
    BRTree T=NULL;
    CreatRBTree(&T);
    while(input)
    {
        printf("请输入你要删除的元素,并以0结束:>");
        scanf("%d",&input);
        if(input==0)break;
        DelNode(&T, input);
        printRBTree(T);
    }
    return 0;
}

运行效果:

 

删除的效果:

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值