平衡二叉树(AVL)学习分享

使用的语言:C++(只是为了简化代码而使用了C++的引用参数的特性,大部分代码都是c语言风格的,并没有使用到C++的高级特性,因此可以将实现代码当成C语言代码进行理解)


1.定义

关于AVL的实现,使用了平衡因子来判断树是否失衡,虽然增加了空间复杂度,但是比起通过判断树的深度要节省时间。结构体的定义如下:

enum Status
{
    OK,ERROR,YES,NO
};
#define LH +1
#define EH 0
#define RH -1
typedef int RcdType;
typedef struct BBSTNode{
    RcdType data;
    int bf;//平衡因子
    struct BBSTNode *lchild,*rchild;
} * BBSTree;

2.插入

插入实现

Status InsertAVL(BBSTree &T,RcdType e,Status &taller);

    关于平衡二叉树的插入,使用递归实现,具体可分为以下三个步骤:

  1. 若根节点为空,则以插入的节点为根节点。
  2. 若插入的节点和根节点相同,则直接返回。
  3. 如果插入的节点比根节点小,则递归访问根节点的左子树,反之则访问右子树。回到步骤1。

    以上为递归调用的基本架子,接下来就是关于树的平衡调节。这里在函数使用了taller这个引用行参,用来记录当前函数下的树T的子树是否变高。下面给出插入函数的代码。

//插入
Status InsertAVL(BBSTree &T,RcdType e,Status &taller)
{
    //如果为空则以该节点为根创建一棵新的树
    if(NULL==T)
    {
        T=(BBSTree)malloc(sizeof(BBSTNode));
        if(NULL==T)return NO;
        T->data=e;
        T->bf=EH;
        taller=YES;
        T->lchild=T->rchild=NULL;
    }
    //如果该节点已经存在,那么就直接返回
    else if (e==T->data)
    {
        taller = NO;
        return NO;
    }
    else if(e<T->data)
    {
        if(NO==InsertAVL(T->lchild,e,taller))return NO;
        if(YES==taller)//树变高了
        {
            switch(T->bf)
            {
                //本来左子树就高的话那么就失衡了
                case LH:L_Balance(T);taller=NO;break;
                case RH:T->bf=EH;taller=NO;break;
                case EH:T->bf=LH;taller=YES;break;
            }
        }
    }
    else
    {
        if(NO==InsertAVL(T->rchild,e,taller))return NO;
        if(YES==taller)
        {
            switch(T->bf)
            {
                case RH:R_Balance(T);taller=NO;break;
                case LH:T->bf=EH;taller=NO;break;
                case EH:T->bf=RH;taller=YES;break;
            }
        }
    }
    return YES;
}

平衡调整函数

    通过判断树的平衡因子来判断树是否失衡,依靠taller向上传递变高的信息。插入的代码很简单,关键的难点是在
Status L_D_Balance(BBSTree &T);
Status R_D_Balance(BBSTree &T);
这两个函数,通过这两个函数可以实现树的失衡旋转平衡因子的调整。由于左右的对称性,因此只需要写出其中一个函数,右边的函数也可以直接得出。

关于失衡调整函数,主要分为两个部分,一个是树的平衡调整,另外则是平衡因子的调整了。

树的平衡调整

    下面介绍左边失衡的情况。调整失衡函数的重点在于对最小失衡子树(即从插入的节点向上寻找到的第一个失衡的节点,这里通过上面的插入函数算法已经找到了)的操作。左子树的失衡分两种情况:(右子树的情况则只需要将L换成R,R换成L理解即可)

  1. LL型:在最小失衡子树的左孩子左子树上插入了新节点
  2. LR型:在最小失衡子树的左孩子右子树上插入了新节点
    在这里插入图片描述
        (注意这里的判断依据是在子树上插入节点)
      关于上述的情况,分别可以使用两种旋转方法进行平衡调整(这里可以结合只有三个节点的情况进行理解),针对LL型,只需要将节点顺时针旋转(右旋)即可,而对于LR型,则需先将左子树逆时针旋转(左旋)(因为左子树比其右孩子的值要小),接下来再进行顺时针旋转(右旋)即可。
    在这里插入图片描述

这个步骤的实现代码十分的简单

//左旋转
void L_Rotation(BBSTree &T)
{
    BBSTree t;
    t = T->rchild;
    T->rchild = t->lchild;
    t->lchild = T;
    T = t;
}
//右旋转
void R_Rotation(BBSTree &T)
{
    BBSTree t;
    t = T->lchild;
    T->lchild = t->rchild;
    t->rchild = T;
    T = t;
}
树的平衡因子调整

    光只是调整树的平衡是不行的,还需要调整树的平衡因子。这里先贴一下代码,平衡因子的调整和树的平衡调整结合在一起,通过平衡因子判断树的失衡情况,并通过左/右旋函数调整平衡。

//插入之后树T失衡时的平衡函数
//这里对三个节点进行定义
//ROOT=原来的树T
//RL=树T的左孩子
//RLR=树T的左孩子的右孩子
void L_Balance(BBSTree &T)
{
    BBSTree lc,rc;
    lc = T->lchild;
    //通过判断RL失衡的情况为LL还是LR
    switch(lc->bf)
    {
        //LL type
        //这种情况下平衡因子改变的节点只有ROOT和RL
        case LH:
        T->bf = lc->bf = EH;
        break;
        //LR type
        case RH://右子树高则说明插入的节点在RLR这棵树上
        rc = lc->rchild;
        //判断RLR的平衡因子
        //这种情况下平衡因子发生改变的节点为上述定义的三个节点
        switch(rc->bf)
        {
            //RLR的左子树高,旋转之后该左子树接到了RL的右子树上
            //由于RL一定不存在右子树,且左子树高度一定为1,所以RL的平衡因子为EH
            //ROOT右旋变成RLR的右子树,因此失去了左子树,并且由于RLR没有右子树,因此ROOT的变成右高
            case LH:T->bf=RH;lc->bf=EH;break;
            //ROOT这棵树只有三个节点的情况,包括ROOT,即ROOT的右子树为空,左子树高度为2
            case EH:T->bf=lc->bf=EH;break;
            //这种情况和上面左高的情况类似。
            case RH:T->bf=EH;lc->bf=LH;break;
        }
        rc->bf = EH;//通过调整之后RLR成为新的树根,左右子树等高
        //先左旋RL,将其转化成LL情况,再进行右旋
        L_Rotation(T->lchild);
        break;
    }
    R_Rotation(T);
}

关于平衡因子的调整,可以参照注释,但画图理解可能会快一点吧。。。


至此关于插入左子树的失衡情况处理这里就说明完毕了。


3.删除

有了插入的基础,删除就会简单一点了,代码基本上没什么变化,主要是删除多了一种插入没有的情况,下面举一个右失衡的情况。
在这里插入图片描述

平衡调整函数

如果是按照上面平衡调整的代码,则不会处理右子树平衡因子为EH的情况,这里博主则将删除的平衡调整函数根据插入的函数更改了一下
下面以右失衡为例,平衡因子的调整则只需要考虑EH的情况即可,其他的情况和插入时相同。

//删除之后右子树失衡时的平衡函数
//和左子树的操作是对称相反的
Status R_D_Balance(BBSTree &T)
{
    BBSTree lc,rc;
    rc = T->rchild;
    Status result;
    switch(rc->bf)
    {
        case RH:
        T->bf = rc->bf = EH;
        result =  YES;
        break;
        case LH:
        lc = rc->lchild;
        switch(lc->bf)
        {
            case RH:T->bf=LH;rc->bf=EH;break;
            case EH:T->bf=rc->bf=EH;break;
            case LH:T->bf=EH;rc->bf=RH;break;
        }
        lc->bf = EH;
        R_Rotation(T->rchild);
        result =  YES;
        break;
        case EH:
        T->bf=RH;rc->bf=LH;
        result = NO;
        break;
    }
    L_Rotation(T);
    return result;
}
}

删除实现

基本上是在插入的架子上改的,注意这里找到了要删除的元素之后,会去找删除元素的中序后继(即这颗树上比自己大的第一个节点),然后交换两者的值,继续删除,具体可以看代码。

//删除
Status DeleteAVL(BBSTree &T,RcdType e,Status &shorter)
{
    //找不到则返回
    if(NULL==T)
    {
        return NO;
    }
    else if (e<T->data)
    {
        if (NO==DeleteAVL(T->lchild,e,shorter))return NO;
        if(YES==shorter)
        {
            //判断祖先节点的高度
            switch(T->bf)
            {
                //如果本来左子树高的话,删除节点之后就应该变矮了
                case LH:T->bf=EH;shorter=YES;break;
                case EH:T->bf=RH;shorter=NO;break;
                case RH:shorter=R_D_Balance(T);break;
            }
        }
    }
    else if(e>T->data)
    {
        if (NO==DeleteAVL(T->rchild,e,shorter))return NO;
        if(YES==shorter)
        {
            switch(T->bf)
            {
                case RH:T->bf=EH;shorter=YES;break;
                case EH:T->bf=LH;shorter=NO;break;
                case LH:shorter=L_D_Balance(T);break;
            }
        }
    }
    else //找到要删除的节点
    {
        BBSTree p;
        p = T;
        //若左右子树都存在,那么寻找左子树的中序后继
        if(NULL!=T->lchild && NULL != T->rchild)
        {
                p = T->lchild;
                while (p->rchild != NULL) {
                    p = p->rchild;
                }
                //并将后继的值赋给该节点
                T->data = p->data;
                //接下来通过遍历该节点的左子树,找到后继删除
                DeleteAVL(T->lchild,p->data,shorter);
                if (shorter==YES)
                {
                    switch(T->bf)
                    {
                        case LH:T->bf=EH;shorter=YES;break;
                        case EH:T->bf=RH;shorter=NO;break;
                        case RH:shorter=R_D_Balance(T);break;
                    }
                }
        }
        else //选择左子树或者右子树接上
        {
            T = (NULL==T->lchild)?T->rchild:T->lchild;
            free(p);
            shorter = YES;
        }
    }
    return YES;
}

以上则是平衡二叉树的插入删除实现了,具体代码可以看这里


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值