不完全解读AVL平衡树

AVL平衡树

封面4 拷贝.png

作者:老九—技术大黍

产品:查看原文

社交:知乎

公众号:老九学堂(新手有福利)

特别声明:原创不易,未经授权不得转载或抄袭,如需转载可联系笔者授权

前言

在讲解算法时我先参考了O'Reilly出版的《Mastering Algorithms with C》、Wiki网站和严蔚敏的《数据结构(C语言版)》教材。因为,我们希望通过多方面专业的参考,希望学习算法时是非常严谨的。

AVL树概述

O'Reilly描述

在《Mastering Algorithms with C》一书中我没有找到关于AVL树概念的描述,在它的Trees章节的Description of Binary Trees有如下内容:

  • Traversal Methods
    • Preorder traversal
    • Inorder traversal
    • Postorder traversal
    • Level-order traversal
  • Tree Balancing

原文描述截图如下:

image-20210319101217324.png

我这里不再参考翻译了,请大家自行百度理解,或者寻找其他方法领悟,因为最后我们参考老严的书给出一个自我认知的说法。

维基描述

image-20210319101546448.png

我在这里也不做参考翻译了,列出来是帮助大家可以多维度理解AVL树正确的概念,希望能够帮助到大家,如果大家还有什么更专业的好书,希望大家也共享出来。

数据结构(C语言版)的解读

下面的描述是根据老严的数据结构教材,结合自己的理解来解读的说法,请大家不要当成标准,如果有不足之处请指正和补充。

平衡二叉树 (AVL树)的定义

它或者是一颗空树,或者具有以下性质的二叉树:它的左子树和右子树的深度之差(平衡因子)的绝对值不超过1,且它的左子树和右子树都是一颗平衡二叉树。

平衡因子的定义

结点的左子树的深度减去右子树的深度,那么显然-1≤平衡因子≤1。

image-20210319101926720.png

AVL树是根据它的发明者G.M.Adelson-Velsky和E.M.Landis命名的。它是最先发明的自平衡二叉查找树,也被称为高度平衡树。相比于“二叉查找树”,AVL树中任何节点的两个子树的高度最大差别为1。

AVL树是严格的平衡二叉树,平衡条件必须满足(所有节点的左右子树高度差不超过1)。不管我们是执行插入还是删除操作,只要不满足上面的条件,就要通过旋转来保持平衡,而旋转是非常耗时的,由此我们可以知道AVL树适合用于插入删除次数比较少,但查找多的情况。

由于维护这种高度平衡所付出的代价比从中获得的效率收益还大,故而实际的应用不多,更多的地方是用追求局部而不是非常严格整体平衡的红黑树(如下图所示)。当然,如果应用场景中对插入删除不频繁,只是对查找要求较高,那么AVL树还是较优于红黑树。

image-20210319102520241.png

AVL树的实现原理

平衡检测

AVL树的操作基本和二叉查找树一样,重点关注两个变化很大的操作:插入和删除。

对于一个二叉排序树来说,每次插入的元素只可能放在叶子结点上。所以只能影响某个子树是否平衡,对其他子树不会有任何的影响。在这种情况下,我们只需要根据搜索的路径,从孩子往祖先找,如果有不平衡的结点就可以被找到;如果一直到根结点都没有发现不平衡结点,则可以认为这次的插入操作没有造成树的不平衡。

在每次删除元素的时,我们同样可以根据搜索的路径,从孩子往祖先找,如果有不平衡的结点就可以被找到;如果一直到根结点都没有发现不平衡结点,则可以认为这次的插入操作没有造成树的不平衡。

这样的搜索过程,我们称作平衡检测

重平衡

在平衡检测过程中,根据AVL树的定义,计算得到的平衡因子会出现两种情况:

  • 如果平衡因子是0,1,-1这三个数的话,可以认定该节点是符合平衡树的定义的;
  • 否则,该结点不平衡,需要重新平衡。

如果发现了某个不平衡的结点,那么就需要对该结点进行重平衡。实现重平衡的方法,是对该节点的子树进行旋转(rotation)。

旋转在理论上有两种情况:

  • 一种称为左旋转(关于X结点的左旋转);
  • 一种称为右旋转(关于Y结点的右旋转)。

image-20210319103237028.png

在真实的情况下,我们会遇到四种可能出现的情况:

image-20210319103258602.png

图中的N,X,Y,Z所代表的含义如下:

  • N代表新插入的元素结点;

  • Z代表从插入元素的位置开始,逆插入元素时的访问结点的顺序,从孩子向祖先方向开始检测平衡因子时发现的第一个不平衡结点;

  • Y代表插入元素时的访问结点路径上,访问z结点之后访问的结点;

  • X代表插入元素时的访问结点路径上,访问y结点之后访问的结点。

这四种情况,都可以通过一次或者两次的旋转,来使得不平衡的结点变平衡。其中,第1和第4种情况可以通过单次旋转重新平衡,第2和第3种情况可以通过双次旋转重新平衡。

单次旋转(Singly Rotation)

单次旋转得到重新平衡的子树的示意图如下所示,需要注意的是分清对应的结点。

image-20210319103422785.png

双次旋转(Doubly Rotation)

双次旋转顾名思义,就是要进行两次旋转来使子树重新平衡,流程如下图示:

  • 在第1种情况下,需要先对y结点进行一次左旋转,然后再对z结点进行一次右旋转;
  • 在第3种情况下,需要先对y结点进行一次右旋转,然后再对z结点进行一次左旋转。

image-20210319103600874.png

AVL树C语言实现

AVL树的结点

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

struct AvlNode
{
    ElementType Element;
    AvlTree Left;
    AvlTree Right;
    int Height;
};
复制代码

AVL树的数据结构

AvlTree MakeEmpty(AvlTree T);
Position Find(ElementType X, AvlTree T);
Position FindMin(AvlTree T);
Position FindMax(AvlTree T);
AvlTree Insert(ElementType X, AvlTree T);
ElementType Retrieve(Position P);
static int Height(Position P);
static int Max(int, int);
static Position SingleRotateWithLeft(Position P);
static Position StingleRotateWithRight(Position P);
static Position DoubleRotateWithLeft(Position P);
static Position DoubleRotateWithRight(Position P);
AvlTree Delete(Position P, AvlTree T);
复制代码

左旋

static Position SingleRotateWithLeft(Position P)
{
    Position K1;
    K1 = P->Left;
    P->Left = K1->Right;
    K1->Right = P;

    P->Height = Max(Height(P->Left), Height(P->Right)) + 1;
    K1->Height = Max(Height(K1->Left), Height(P->Height)) + 1;

    return K1;
}
复制代码

右旋

static Position StingleRotateWithRight(Position P)
{
    Position K1;
    K1 = P->Right;
    P->Right = K1->Left;
    K1->Left = P;

    P->Height = Max(Height(P->Left), Height(P->Right)) + 1;
    K1->Height = Max(Height(K1->Left), Height(P->Height)) + 1;
    
    return K1;
}
复制代码

双次旋转

static Position DoubleRotateWithLeft(Position P)
{
    P->Left = StingleRotateWithRight(P->Left);
    return SingleRotateWithLeft(P);
}

static Position DoubleRotateWithRight(Position P)
{
    P->Right = StingleRotateWithRight(P->Right);
    return StingleRotateWithRight(P);
}
复制代码

插入元素(Insert)

AvlTree Insert(ElementType X, AvlTree T)
{
    if (T == NULL)
    {
        T = malloc(sizeof(struct AvlNode));
        if (T == NULL)
        {
            Error("Error: out of space!!");
        }
        else
        {
            T->Element = X;
            T->Left = T->Right = NULL;
            T->Height = 0;
        }
    }
    else if (X < T->Element)
    {
        T->Left = Insert(X, T->Left);
        if (Height(T->Left) - Height(T->Right) == 2)
        {
            if (X < T->Left->Element)
            {
                T = SingleRotateWithLeft(T);
            }
            else
            {
                T = DoubleRotateWithLeft(T);
            }
        }
    }   
    else
    {
        T->Right = Insert(X, T->Right);
        if (Height(T->Right) - Height(T->Right) == 2)
        {
            if (X < T->Right->Element)
            {
                T = StingleRotateWithRight(T);
            }
            else
            {
                T = DoubleRotateWithRight(T);
            }
        }
    }
    T->Height = Max(Height(T->Left), Height(T->Right)) + 1;
    return T;
}
复制代码

删除元素(Delete)

AvlTree Delete(Position P, AvlTree T)
{
    Position PMix; 
    Position Tmp;
    if (T != NULL)
    {
        if (T->Element > P->Left)
        {
            T->Left = Delete(P, T->Left);
            T->Height = Max(Height(T->Right), Height(T->Left)) + 1;
            if (Height(T->Right) - Height(T->Left) == 2)
            {
                if (T->Right->Element < P->Element)
                {
                    return StingleRotateWithRight(T);
                }
                else
                {
                    return DoubleRotateWithRight(T);
                }
            }
        }
        else if (T->Element < P->Left)
        {
            T->Right = Delete(P, T->Right);
            T->Height = Max(Height(T->Right), Height(T->Left)) + 1;
            if (Height(T->Left) - Height(T->Right) == 2)
            {
                if (T->Left->Element > P->Element)
                {
                    return SingleRotateWithLeft(T);
                }
                else
                {
                    return DoubleRotateWithLeft(T);
                }
            }
        }
        else
        {
            if (T->Right != NULL && T->Left != NULL)
            {
                if (Height(T->Right) > Height(T->Left))
                {
                    PMix = FindMin(T->Right);
                    T->Element = PMix->Element;
                    T->Right = Delete(PMix,T->Right);
                }
                else
                {
                    PMix = FindMax(T->Left);
                    T->Element = PMix->Element;
                    T->Left = Delete(PMix,T->Left);
                }
                T->Height = Max(Height(T->Right), Height(T->Left)) + 1;
            }
            else
            {
                Tmp = P;
                T = P->Right ? P->Right: P->Left;
                free(Tmp);
            }
        }
    }
    return T;
}
复制代码

搜索(Search)

Position Find(ElementType X, AvlTree T)
{
    if (T == NULL)
    {
        return NULL;
    }
    else if(X < T->Element)
    {
        return Find(X, T->Left);
    }
    else if (X > T->Element)
    {
        return Find(X, T->Right);
    }
    else
    {
        return T;
    }
}

Position FindMax(AvlTree T)
{
    Position pL = T;
    Position pR = T;
    if(T->Left != NULL)
    {
        pL = findMax(T->Left);
    }

    if(T->Right != NULL)
    {
        pR = findMax(T->Right);
    }

    return pL->Height > pR->Height ? pL : pR;
}

Position FindMin(AvlTree T)
{
    Position pL = T;
    Position pR = T;

    if(T->Left != NULL)
    {
        pL = findMin(T->Left);
    }

    if(T->Right != NULL)
    {
        pR = findMin(T->Right);
    }

    return pL->Height < pR->Height ? pL : pR;
}
复制代码

辅助函数

void MakeEmpty(AvlTree T)
{
    *T = (AvlTree)malloc(sizeof(AvlNode));
    (*T).Left = NULL;
    (*T).Right = NULL;
}

static int Height(Position P)
{
    return P->Height;
}

ElementType Retrieve(Position P)
{
    return P->Element;
}

static int Max(int x, int y)
{
    return x > y ? x : y;
}

总结

在《Essential Algorithms》一书这样描述算法的特点:

image-20210319115701603.png

我们这个C语言实现肯定不是最好的,这里只是抛砖引玉,希望与给大家在学习算法时提供思路和帮助。如果有不足之处,请大家指正和补充。

最后

感觉有用的同学,请记得给大黍❤️关注+点赞+收藏+评论+转发❤️

作者:老九学堂—技术大黍

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值