AVL树的介绍

平衡二叉树,又称AVL(Adelson-Velskii和Landis)树,是带有平衡条件的二叉查找树。这个平衡条件必须要容易保持,而且它必须保证树的深度是 O(log N)。一棵AVL树是其每个节点的左子树和右子树的高度最多差1的二叉查找树( 空树的高度定义为 -1 )。查找、插入和删除在平均和最坏情况下都是 O(log n)。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。可以证明,大致上讲,一个AVL树的高度最多为 1.44log( N  + 2 ) - 1.328,但是实际上的高度只比 log N 稍微多一些。

平衡因子( Balance Factor,简称BF) : BF(T) = hL - hR( 有时相反 ),其中 hL 和 hR 分别为 T 的左、右子树的高度。一棵平衡二叉树其 | BF | <= 1;带有平衡因子 -2 或 2 的节点被认为是不平衡的,并需要重新平衡这个树。平衡因子可以直接存储在每个节点中,或从可能存储在节点中的子树高度计算出来。

                                                 

非AVL树的例子                                                                                                 同一个树在平衡之后的样子

AVL节点数计算

高度为h的AVL树,节点数N最多; 最少( 其中 )

最少节点数n如以斐波那契数列可以用数学归纳法证明:
 - 1 (是Fibonacci polynomial)。

即:
= 0 (表示AVL Tree高度为0的节点总数)

= 1 (表示AVL Tree高度为1的节点总数)

= 2 (表示AVL Tree高度为2的节点总数)

 + 1 (表示AVL Tree高度为h的节点总数)

换句话说,当节点数为N时,高度h最多为

 AVL树的操作

AVL树的基本操作一般涉及运作同在不平衡的二叉查找树所运作的同样的算法。但是要进行预先或随后做一次或多次所谓的"AVL旋转"。

以下图表以四列表示四种情况,每行表示在该种情况下要进行的操作。在左左和右右的情况下,只需要进行一次旋转操作;在左右和右左的情况下,需要进行两次旋转操作。

实现描述:

假设平衡因子是左子树的高度减去右子树的高度所得到的值,又假设由于在二叉排序树上插入节点而失去平衡的最小子树根节点的指针为a(即a是离插入点最近,且平衡因子绝对值超过1的祖先节点),则失去平衡后进行的规律可归纳为下列四种情况:

  1. 单向右旋平衡处理LL:由于在*a的左子树根节点的左子树上插入节点,*a的平衡因子由1增至2,致使以*a为根的子树失去平衡,则需进行一次右旋转操作;
  2. 单向左旋平衡处理RR:由于在*a的右子树根节点的右子树上插入节点,*a的平衡因子由-1变为-2,致使以*a为根的子树失去平衡,则需进行一次左旋转操作;
  3. 双向旋转(先左后右)平衡处理LR:由于在*a的左子树根节点的右子树上插入节点,*a的平衡因子由1增至2,致使以*a为根的子树失去平衡,则需进行两次旋转(先左旋后右旋)操作。
  4. 双向旋转(先右后左)平衡处理RL:由于在*a的右子树根节点的左子树上插入节点,*a的平衡因子由-1变为-2,致使以*a为根的子树失去平衡,则需进行两次旋转(先右旋后左旋)操作。

在平衡的二叉排序树BBST (Balancing Binary Search Tree)上插入一个新的数据元素e的递归算法可描述如下:

  1. 若BBST为空树,则插入一个数据元素为e的新节点作为BBST的根节点,树的深度增1;
  2. 若e的关键字和BBST的根节点的关键字相等,则不进行;
  3. 若e的关键字小于BBST的根节点的关键字,而且在BBST的左子树中不存在和e有相同关键字的节点,则将e插入在BBST的左子树上,并且当插入之后的左子树深度增加(+1)时,分别就下列不同情况处理之:若e的关键字大于BBST的根节点的关键字,而且在BBST的右子树中不存在和e有相同关键字的节点,则将e插入在BBST的右子树上,并且当插入之后的右子树深度增加(+1)时,分别就不同情况处理之。
    1. BBST的根节点的平衡因子为-1(右子树的深度大于左子树的深度,则将根节点的平衡因子更改为0,BBST的深度不变;
    2. BBST的根节点的平衡因子为0(左、右子树的深度相等):则将根节点的平衡因子更改为1,BBST的深度增1;
    3. BBST的根节点的平衡因子为1(左子树的深度大于右子树的深度):则若BBST的左子树根节点的平衡因子为1:则需进行单向右旋平衡处理,并且在右旋处理之后,将根节点和其右子树根节点的平衡因子更改为0,树的深度不变;
  4. 若e的关键字大于BBST的根节点的关键字,而且在BBST的右子树中不存在和e有相同关键字的节点,则将e插入在BBST的右子树上,并且当插入之后的右子树深度增加(+1)时,分别就不同情况处理之。

 实现代码:

Avl树定义

struct AvlNode;
typedef struct AvlNode *Position;
typedef struct AvlNode *AvlTree;

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

两个辅助函数

static int Height( Position P )
{
    if( P == NULL )
        return -1;
    else
        return P->Height;
}

static int Max( int Lhs, int Rhs )
{
    return Lhs > Rhs ? Lhs : Rhs;
}

基本操作

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

Position FindMin( AvlTree T )
{
    if( T == NULL )
        return NULL;
    else if( T->Left == NULL )
        return T;
    else
        return FindMin( T->Left );
}

Position FindMax( AvlTree T )
{
    if( T != NULL )
        while( T->Right != NULL )
            T = T->Right;

    return T;
}

四种情况下的旋转

1、LL型(单次右旋转)

k2的左子树比右子树高2,所以不平衡,我们需要把k1提到k2的位置,然后k2下降到k1的右子树,最后还有把k1的右子树放到k2的左子树即可。

/* This function can be called only if K2 has a left child */
/* Perform a rotate between a node (K2) and its left child */
/* Update heights, then return new root */
//LL型旋转(单次右旋)
static Position SingleRotateWithLeft( Position K2 )
{
    Position K1;

    K1 = K2->Left;          //找到 K2 左子树
    K2->Left = K1->Right;   //将 K1 右子树移到 K2 的左子树
    K1->Right = K2;         //建立 K1 和 K2 的关系 

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

    return K1;  /* New root */
}

2、RR型(单次右旋转) 

与LL型对称,原理类似

/* This function can be called only if K1 has a right child */
/* Perform a rotate between a node (K1) and its right child */
/* Update heights, then return new root */
//RR型旋转(单次左旋)
//RR型与LL型为对称,原理类似
static Position SingleRotateWithRight( Position K1 )
{
    Position K2;

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

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

    return K2;  /* New root */
}

3、LR型双旋转(单次左旋后右旋)

先以K3的左子树为根节点执行一次左左旋转,然后以k3的根节点,执行一次右右旋转即可。

/* This function can be called only if K3 has a left */
/* child and K3's left child has a right child */
/* Do the left-right double rotation */
/* Update heights, then return new root */
//LR型双旋转( 单次左旋(RR型)后单次右旋(LL型))
static Position DoubleRotateWithLeft( Position K3 )
{
    /* Rotate between K1 and K2 */
    K3->Left = SingleRotateWithRight( K3->Left );

    /* Rotate between K3 and K2 */
    return SingleRotateWithLeft( K3 );
}

4、RL型双旋转(单次右旋后单次左旋)

与LR型双旋转对称,原理类似

/* This function can be called only if K1 has a right */
/* child and K1's right child has a left child */
/* Do the right-left double rotation */
/* Update heights, then return new root */
//RL型双旋转 ( 单次右旋(LL型) 后单次左旋(RR型) )
static Position DoubleRotateWithRight( Position K1 )
{
    /* Rotate between K3 and K2 */
    K1->Right = SingleRotateWithLeft( K1->Right );

    /* Rotate between K1 and K2 */
    return SingleRotateWithRight( K1 );
}

5、结点插入

AVL树进行插入操作时,会破坏二叉树的平衡性,所以每当插入一个数据时,都要从插入点往上,一步一步检测出是否出现平衡因子大于1的,如果有,则要做相应的旋转。

AvlTree Insert( ElementType X, AvlTree T )
{
    if( T == NULL )
    {
        /* Create and return a one-node tree */
        T = malloc( sizeof( struct AvlNode ) );
        if( T == NULL )
            FatalError( "Out of space!!!" );
        else
        {
            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 )  //判断子树是否平衡
            if( X < T->Left->Element )                     //比当前结点左子树的值小,即为左左情形。
                T = SingleRotateWithLeft( T );             //LL旋转(右单旋转)
            else                                           //否则为左右情形
                T = DoubleRotateWithLeft( T );             //LR旋转
    }
    else if( X > T->Element )                              //插入值比当前结点值大,往右子树插入
    {
        T->Right = Insert( X, T->Right );                  //递归插入
        if( Height( T->Right ) - Height( T->Left ) == 2 )  //判断子树是否平衡
            if( X > T->Right->Element )                    //比当前结点右子树大,即为右右情形
                T = SingleRotateWithRight( T );            //RR旋转(左单旋转)
            else                                           //否则为右左情形
                T = DoubleRotateWithRight( T );            //RL旋转
    }
    /* Else X is in the tree already; we'll do nothing */

    T->Height = Max( Height( T->Left ), Height( T->Right ) ) + 1;
    return T;
}

6、结点删除

结点删除包括四种情况:

  • 左右子树都非空
  • 左子树为空,右子树非空
  • 右子树为空,左子树非空
  • 左右子树都为空
AvlTree Delete(ElementType X,AvlTree T)
{
    if (T == NULL)                        //空树直接返回
        return NULL;
    if (x < T->Element)                   //删除值小于当前节点,说明删除节点在当前节点左侧
    {
        T->Left = Delete(X,T->Left);
        if (Height(T->Right) - Height(T->Left) == 2)
        {
            if (Height(T->Right->Left) > Height(T->Right->Right))
                T = DoubleRotateRL(T);
            else
                T = SingleRotateRight(T);
        }
    }
    else if (X > T->Element)             //删除节点在当前节点右侧
    {
        T->Right = Delete(X,T->Right);
        if (Height(T->Left) - Height(T->Right) == 2)
        {
            if (Height(T->Left->Right) > Height(T->Left->Left))
                T = DoubleRotateLR(T);
            else
                T = SingleRotateLeft(T);
        }
    }
    else                                 //找到删除节点
    {
        if (T->Right)                    //右子树不为空的情况
        {
            AvlTree tmp = T->Right;
            while (tmp->Left != NULL) tmp = tmp->Left;  //找到要删除的结点的右子树的左子树最小值,以便替要删除的结点
            T->Element = tmp->Element;
            T->height = tmp->height;
            T->Right = Delete(tmp->Element,T->Right); //递归删除
        }
        else
        {
            //右子树为空的情况,free节点,返回被删除节点的左节点
            //这也是真正删除节点的地方
            AvlTree tmp=T;
            T = T->Left;
            free(emp);
            return T;
        }
    }
    //每次删除之后,都要更新节点的高度
    T->height = Max(Height(T->Left),Height(T->Right)) + 1;
    return T;
}

完整代码

struct AvlNode;
typedef struct AvlNode *Position;
typedef struct AvlNode *AvlTree;

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

AvlTree MakeEmpty( AvlTree T )
{
    if( T != NULL )
    {
        MakeEmpty( T->Left );
        MakeEmpty( T->Right );
        free( T );
    }
    return NULL;
}

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

Position FindMin( AvlTree T )
{
    if( T == NULL )
        return NULL;
    else if( T->Left == NULL )
        return T;
    else
        return FindMin( T->Left );
}

Position FindMax( AvlTree T )
{
    if( T != NULL )
        while( T->Right != NULL )
            T = T->Right;

    return T;
}

static int Height( Position P )
{
    if( P == NULL )
        return -1;
    else
        return P->Height;
}

static int Max( int Lhs, int Rhs )
{
    return Lhs > Rhs ? Lhs : Rhs;
}

/* This function can be called only if K2 has a left child */
/* Perform a rotate between a node (K2) and its left child */
/* Update heights, then return new root */
//LL型旋转(单次右旋)
static Position SingleRotateWithLeft( Position K2 )
{
    Position K1;

    K1 = K2->Left;          //找到 K2 左子树
    K2->Left = K1->Right;   //将 K1 右子树移到 K2 的左子树
    K1->Right = K2;         //建立 K1 和 K2 的关系 

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

    return K1;  /* New root */
}

/* This function can be called only if K1 has a right child */
/* Perform a rotate between a node (K1) and its right child */
/* Update heights, then return new root */
//RR型旋转(单次左旋)
//RR型与LL型为对称,原理类似
static Position SingleRotateWithRight( Position K1 )
{
    Position K2;

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

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

    return K2;  /* New root */
}

/* This function can be called only if K3 has a left */
/* child and K3's left child has a right child */
/* Do the left-right double rotation */
/* Update heights, then return new root */
//LR型双旋转( 单次左旋(RR型)后单次右旋(LL型))
static Position DoubleRotateWithLeft( Position K3 )
{
    /* Rotate between K1 and K2 */
    K3->Left = SingleRotateWithRight( K3->Left );

    /* Rotate between K3 and K2 */
    return SingleRotateWithLeft( K3 );
}

/* This function can be called only if K1 has a right */
/* child and K1's right child has a left child */
/* Do the right-left double rotation */
/* Update heights, then return new root */
//RL型双旋转 ( 单次右旋(LL型) 后单次左旋(RR型) )
static Position DoubleRotateWithRight( Position K1 )
{
    /* Rotate between K3 and K2 */
    K1->Right = SingleRotateWithLeft( K1->Right );

    /* Rotate between K1 and K2 */
    return SingleRotateWithRight( K1 );
}

AvlTree Insert( ElementType X, AvlTree T )
{
    if( T == NULL )
    {
        /* Create and return a one-node tree */
        T = malloc( sizeof( struct AvlNode ) );
        if( T == NULL )
            FatalError( "Out of space!!!" );
        else
        {
            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 )  //判断子树是否平衡
            if( X < T->Left->Element )                     //比当前结点左子树的值小,即为左左情形。
                T = SingleRotateWithLeft( T );             //LL旋转(右单旋转)
            else                                           //否则为左右情形
                T = DoubleRotateWithLeft( T );             //LR旋转
    }
    else if( X > T->Element )                              //插入值比当前结点值大,往右子树插入
    {
        T->Right = Insert( X, T->Right );                  //递归插入
        if( Height( T->Right ) - Height( T->Left ) == 2 )  //判断子树是否平衡
            if( X > T->Right->Element )                    //比当前结点右子树大,即为右右情形
                T = SingleRotateWithRight( T );            //RR旋转(左单旋转)
            else                                           //否则为右左情形
                T = DoubleRotateWithRight( T );            //RL旋转
    }
    /* Else X is in the tree already; we'll do nothing */

    T->Height = Max( Height( T->Left ), Height( T->Right ) ) + 1;
    return T;
}

AvlTree Delete(ElementType X,AvlTree T)
{
    if (T == NULL)                        //空树直接返回
        return NULL;
    if (x < T->Element)                   //删除值小于当前节点,说明删除节点在当前节点左侧
    {
        T->Left = Delete(X,T->Left);
        if (Height(T->Right) - Height(T->Left) == 2)
        {
            if (Height(T->Right->Left) > Height(T->Right->Right))
                T = DoubleRotateRL(T);
            else
                T = SingleRotateRight(T);
        }
    }
    else if (X > T->Element)             //删除节点在当前节点右侧
    {
        T->Right = Delete(X,T->Right);
        if (Height(T->Left) - Height(T->Right) == 2)
        {
            if (Height(T->Left->Right) > Height(T->Left->Left))
                T = DoubleRotateLR(T);
            else
                T = SingleRotateLeft(T);
        }
    }
    else                                 //找到删除节点
    {
        if (T->Right)                    //右子树不为空的情况
        {
            AvlTree tmp = T->Right;
            while (tmp->Left != NULL) tmp = tmp->Left;  //找到要删除的结点的右子树的左子树最小值,以便替要删除的结点
            T->Element = tmp->Element;
            T->height = tmp->height;
            T->Right = Delete(tmp->Element,T->Right); //递归删除
        }
        else
        {
            //右子树为空的情况,free节点,返回被删除节点的左节点
            //这也是真正删除节点的地方
            AvlTree tmp=T;
            T = T->Left;
            free(emp);
            return T;
        }
    }
    //每次删除之后,都要更新节点的高度
    T->height = Max(Height(T->Left),Height(T->Right)) + 1;
    return T;
}

  

平衡二叉查找树(AVL)的理解与实现的更多相关文章

  1. 006-数据结构-树形结构-二叉树、二叉查找树、平衡二叉查找树-AVL树

    一.概述 树其实就是不包含回路的连通无向图.树其实是范畴更广的图的特例. 树是一种数据结构,它是由n(n>=1)个有限节点组成一个具有层次关系的集合. 1.1.树的特性: 每个结点有零个或多个子 ...

  2. 【查找结构3】平衡二叉查找树 [AVL]

    在上一个专题中,我们在谈论二叉查找树的效率的时候.不同结构的二叉查找树,查找效率有很大的不同(单支树结构的查找效率退化成了顺序查找).如何解决这个问题呢?关键在于如何最大限度的减小树的深度.正是基于这 ...

  3. 平衡二叉查找树 AVL 的实现

    不同结构的二叉查找树,查找效率有很大的不同(单支树结构的查找效率退化成了顺序查找).如何解决这个问题呢?关键在于如何最大限度的减小树的深度.正是基于这个想法,平衡二叉树出现了. 平衡二叉树的定义 (A ...

  4. 面试题:什么叫平衡二叉查找树--AVL树

    查找.插入和删除在平均和最坏情况下都是O(log n) 增加和删除可能需要通过一次或多次树旋转来重新平衡这个树 节点的平衡因子是它的左子树的高度减去它的右子树的高度.带有平衡因子 1.0 或 -1 的 ...

  5. 平衡树初阶——AVL平衡二叉查找树+三大平衡树(Treap + Splay + SBT)模板【超详解】

    平衡树初阶——AVL平衡二叉查找树 一.什么是二叉树 1. 什么是树. 计算机科学里面的树本质是一个树状图.树首先是一个有向无环图,由根节点指向子结点.但是不严格的说,我们也研究无向树.所谓无向树就是 ...

  6. AVL树(平衡二叉查找树)

    首先要说AVL树,我们就必须先说二叉查找树,先介绍二叉查找树的一些特性,然后我们再来说平衡树的一些特性,结合这些特性,然后来介绍AVL树. 一.二叉查找树 1.二叉树查找树的相关特征定义 二叉树查找树 ...

  7. 数据结构-自平衡二叉查找树(AVL)详解

    介绍: 在计算机科学中,AVL树是最先发明的自平衡二叉查找树. 在AVL树中任何节点的两个子树的高度最大差别为一,所以它也被称为高度平衡树. 查找.插入和删除在平均和最坏情况下都是O(log n).增 ...

  8. 算法学习 - 平衡二叉查找树实现(AVL树)

    平衡二叉查找树 平衡二叉查找树是非常早出现的平衡树,由于全部子树的高度差不超过1,所以操作平均为O(logN). 平衡二叉查找树和BS树非常像,插入和删除操作也基本一样.可是每一个节点多了一个高度的信 ...

  9. 树的平衡之AVL树——错过文末你会后悔,信我

    学习数据结构应该是一个循序渐进的过程: 当我们学习数组时,我们要体会数组的优点:仅仅通过下标就可以访问我们要找的元素(便于查找). 此时,我们思考:假如我要在第一个元素前插入一个新元素?采用数组需要挪 ...

  10. AVL树理解

    AVL树理解 简介 我们知道,AVL树也是平衡树中的一种,是自带平衡条件的二叉树,始终都在维护树的高度,保持着树的高度为logN,同时把插入.查找.删除一个结点的时间复杂度的最好和最坏情况都维持在O( ...

随机推荐

  1. IT从业者的职业道路(从程序员到部门经理) - 项目管理系列文章

    十年前,笔者还是一个刚毕业的大学生,对IT业只是停留在学校的编程知识领域.刚出社会,有很多需要学习的地方.在这十年间,笔者经历了程序员,技术经理,项目经理,部门经理等职位.本文就是要说说如何从程序员到 ...

  2. Echarts xAxis boundaryGap

    Echarts  xAxis----->boundaryGap: false 坐标轴两边留白策略,类目轴和非类目轴的设置和表现不一样. 类目轴中 boundaryGap 可以配置为 true 和 ...

  3. goldengate abended with no data found

    先来看下报错ggserr.log: 2016-12-22 04:48:52  WARNING OGG-02544  Unhandled error (ORA-26787: The row with k ...

  4. springMVC 配置CharacterEncodingFilter之后不起效果

    最近开始自学springMVC框架,遇到中文乱码这一经典问题,记录下解决过程,以便后续忘记 web.xml 里过滤器配置如下: <?xml version="1.0" enc ...

  5. 客户端连接RMS服务,报:服务暂时不可用,请确保已连接到此服务器…….

    原因在于客户端office没有安装rms服务模块,或安装的office有缺陷,请重新安装可用的office版本.

  6. 防火墙防DDOS攻击的简单设置

    #Ping洪水攻击(Ping of Death) iptables -A FORWARD -p icmp --icmp-type echo-request -m limit --limit 1/s - ...

  7. C#学习笔记-Windows窗体自定义初始位置

    根据屏幕大小定义初始位置: (这个不是难,但是最近常常忘记,记着方便查看.) //获取当前屏幕的长和宽 int ScreenX = Screen.PrimaryScreen.Bounds.Width; ...

  8. Mysql数据库主从心得整理

    管理mysql主从有2年多了,管理过200多组mysql主从,几乎涉及到各个版本的主从,本博文属于总结性的,有一部分是摘自网络,大部分是根据自己管理的心得和经验所写,整理了一下,分享给各位同行,希望对 ...

  9. 使用scrollpagination实现页面底端自动加载无需翻页功能

    当阅读到页面最底端的时候,会自动显示一个"加载中"的功能,并自动从服务器端无刷新的将内容下载到本地浏览器显示. 这样的自动加载功能是如何实现的?jQuery的插件 ScrollPa ...

  10. ASP.net 实现禁止用户重复登录

    本文先为大家介绍如何利用缓存Cache方便地实现此功能. Cache与Session这二个状态对像的其中有一个不同之处,Cache是一个全局对象,作用的范围是整个应用程序,所有用户:而Session是 ...