AVL树的插入与删除(均为递归实现)

一. 引言

AVL树是带有平衡条件的二叉查找树.这个平衡条件必须要容易保持,而且它必须保证树的深度是O(logN).一颗AVL树是其每个节点的左子树和右子树的高度最多差一的二叉查找树.
主要介绍插入算法和删除算法.

二. AVL树的结点定义:

typedef struct AvlNode *Position;
typedef struct AvlNode *AvlTree;
typedef int ElementType;

struct AvlNode
{
    ElementType Element;
    AvlTree Left;
    AvlTree Right;
    int Height;
};

三. 各种辅助函数:

/* * 返回两个数中的最大值 */
static int Max(int a, int b)
{
    if(a > b)
        return a;
    else
        return b;
}
/* 在AVL树T中寻找元素值最小的节点,并将其返回 */
Position FindMin(AvlTree T)
{
    if(T == NULL)
        return NULL;
    else
    if(T->Left == NULL)
        return T;
    else
        return FindMin(T->Left);
}
/* * 返回节点P的高度 */
static int Height(Position P)
{
    if (P == NULL)
        return -1;
    else
        return P->Height;
}

四. 对AVL树进行修复的函数

1. 进行旋转的函数
左单旋转:
可以看到在结点k2处失去了平衡, 并且是在k2的左儿子的左子树上插入的,因此对k2结点进行左单旋转,即将k1作为新的根节点,将k2作为其右儿子,原来k2的右儿子作为k2的左结点,详见示意图(图丑勿怪^_^).
《AVL树的插入与删除(均为递归实现)》
左单旋转的代码:

/* * 左单旋转 */
static Position SingleRotateWithLeft(Position K2)
{
    Position K1;

    K1 = K2->Left;
    K2->Left = K1->Right;
    K1->Right = K2;

    //K2的子节点的高度都没有变化,不需更改
    K2->Height = Max(Height(K2->Left), Height(K2->Right)) + 1;
    K1->Height = Max(Height(K1->Left), Height(K1->Right)) + 1;

    //返回新的根节点
    return K1;
}

右单旋转的过程和左单旋转的过程基本一样,不做赘述,详见代码.

/* * 右单旋转 */
static Position SingleRotateWithRight(Position K2)
{
    Position K1;

    K1 = K2->Right;
    K2->Right = K1->Left;
    K1->Left = K2;

    //K2的子节点的高度都没有变化
    K2->Height = Max(Height(K2->Left), Height(K2->Right)) + 1;
    K1->Height = Max(Height(K1->Left), Height(K1->Right)) + 1;

    //返回新的根节点
    return K1;
}

2. 左-右双旋转:
从图中可以看出,旋转之前在k2处失去了平衡,但是此时仅在k2时做一次单旋转已经不能保持平衡了,因此进行新的左-右双旋转.从图中可以看出,实际上左-右双旋转相当于先对k3的左儿子k1做一次右单旋转再对k3做一次左单旋转,所以实际上双旋转没有什么新的东西.
《AVL树的插入与删除(均为递归实现)》

左-右双旋转的代码:

/* * 左-右双旋转 */
static Position DoubleRotateWithLeft(Position K3)
{
    K3->Left = SingleRotateWithRight(K3->Left);

    return SingleRotateWithLeft(K3);
}

右-左双旋转代码:

/* * 右-左双旋转 */
static Position DoubleRotateWithRight(Position K3)
{
    K3->Right = SingleRotateWithLeft(K3->Right);

    return SingleRotateWithRight(K3);
}

3. 对失去平衡的节点进行修复的函数
假如结点在k2处失去平衡,则要先判断如何执行旋转,具体判断参照代码:

//对失去平衡的节点进行修复的函数
static Position Fix(Position K2)
{
    if(Height(K2->Left) > Height(K2->Right))
    {
        //K2左儿子的左儿子的高度大于K2的左儿子的右儿子的高度, 执行左单旋转, 否则执行左-右双旋转
        if(Height(K2->Left->Left) > Height(K2->Left->Right))
            K2 = SingleRotateWithLeft(K2);
        else if(Height(K2->Left->Left) < Height(K2->Left->Right))
            K2 = DoubleRotateWithLeft(K2);
    }
    else if(Height(K2->Left) < Height(K2->Right))
    {
        //K2右儿子的右儿子的高度大于K2的右儿子的左儿子的高度, 执行右单旋转, 否则执行右-左双旋转
        if(Height(K2->Right->Right) > Height(K2->Right->Left))
            K2 = SingleRotateWithRight(K2);
        else if(Height(K2->Right->Right) < Height(K2->Right->Left))
            K2 = DoubleRotateWithRight(K2);
    }

    return K2;
}

五. AVL树的插入函数

插入之后要对结点高度进行修改,并且判断是否失去平衡,如果失去平衡则要进行修复.
代码如下:

/*
 *  向Avl树插入节点
 */
AvlTree Insert(ElementType X, AvlTree T)
{
    if(T == NULL)
    {
        //创建并返回一个只有一个节点的树
        T = malloc(sizeof(struct AvlNode));
        if(T == NULL)
        {
            printf("Out of space!!!\n");
            return NULL;
        }
        T->Element = X;
        T->Height = 0;
        T->Left = T->Right = NULL;
    }
    else if(X < T->Element)
    {
        T->Left = Insert(X, T->Left);
        if(Height(T->Left) - Height(T->Right) >= 2)
              T = Fix(T);
    }
    else if(X > T->Element)
    {
        T->Right = Insert(X, T->Right);
        if(Height(T->Right) - Height(T->Left) >= 2)
              T = Fix(T);
    }
    /* 如果X已经在树中了, 那么什么都不做*/

    //修改插入后T节点的高度
    T->Height = Max(Height(T->Left), Height(T->Right)) + 1;

    return T;  //千万不能忘
}

六. AVL树的删除函数

删除过程和二叉查找树的删除过程一样, 用被删除节点的右子树上的最小元素代替被删除的节点, 然后递归删除该最小元.
只不过删除完成后要检查节点高度, 如果不满足AVL树的平衡条件,则要进行旋转修复.
如果删除操作较少,懒惰删除恐怕是最好的策略.
代码如下:

/* AVL树的非懒惰删除 */
Position Delete(ElementType X, AvlTree T)
{
    Position TmpCell;

    if(T == NULL)
    {
        printf("Element not found!!\n");
        return NULL;
    }
    else if(T->Element > X) //往左边查找元素X
        T->Left = Delete(X, T->Left);
    else if(T->Element < X) //往右边查找元素X
        T->Right = Delete(X, T->Right);
    else if(T->Left && T->Right) //元素找到了并且左右子树都不为空
    {
        //将要删除的节点用右子树中的最小节点代替
        TmpCell = FindMin(T->Right);
        T->Element = TmpCell->Element;
        //递归删除该最小节点
        T->Right = Delete(T->Element, T->Right);
    }
    else //元素找到了并且左右子树有一个为空
    {
        TmpCell = T;
        if(T->Left == NULL)
            T = T->Right;   //此处是为了让T的父节点指向T的右子树(由于函数最后返回了T)
        else if(T->Right == NULL)
            T = T->Left;

        free(TmpCell); //释放节点
    }

    if(T != NULL)
    {
        //删除完节点之后要更新高度
        T->Height = Max(Height(T->Left), Height(T->Right)) + 1;
        //在节点T处失去平衡
        if((Height(T->Left) - Height(T->Right) >= 2) || (Height(T->Right) - Height(T->Left) >= 2))
        {
            T = Fix(T);
            //修复完成后更新节点高度
            T->Height = Max(Height(T->Left), Height(T->Right)) + 1;
        }
    }

    return T;   //千万不能忘记
}

参考资料: <数据结构与算法分析–C语言描述(第二版)>

    原文作者:AVL树
    原文地址: https://blog.csdn.net/ytang007/article/details/52577761
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞