大话数据结构学习笔记 - 查找之平衡二叉树(AVL)及其C实现

大话数据结构学习笔记 – 查找之平衡二叉树(AVL)及其C实现

平衡二叉树(AVL树)

平衡二叉树(Self-Balancing Binary Search TreeHeight-Balanced Binary Search Tree)是一种二叉排序树, 其中每一个节点的左子树和右子树的高度差至多等于1. 平衡二叉树是高度平衡的二叉排序树,高度平衡即要么是空树,要么其左子树和右子树都是平衡二叉树,且左子树和右子树的高度差不超过1

  • 平衡因子BF(Balance Factor): 二叉树结点的左子树深度减去右子树深度的值
  • 最小不平衡树: 距离插入节点最近的,且平衡因子的绝对值大于1的结点为根的子树

二叉树结构

typedef struct AVLTNode
{
    ElementType data;
    int height;
    struct AVLTNode *lchild, *rchild;
}AVLTNode, *AVLTree;

旋转

节点的插入和删除都会影响到平衡二叉树的结构,进而影响到某些结点的平衡因子, 若平衡因子的取值不是{-1, 0, 1}的话,表明该AVL树失去平衡,则此时需要通过旋转使AVL树重新平衡。 失衡的情况分为以下几种

《大话数据结构学习笔记 - 查找之平衡二叉树(AVL)及其C实现》

上面四种都是失去平衡的AVL树,依次记为LL, LR, RL, RR。对于每种失衡情况,还有其他的形式

《大话数据结构学习笔记 - 查找之平衡二叉树(AVL)及其C实现》
总得来说,失去平衡的AVL树一定符合上述四种类型

  • LLLeftLeft, 也称为左左。 在某结点的左子树的左子树上插入或在某结点的右子树上删除一个节点后,导致该结点的平衡因子变为2, 则该结点成为最小不平衡树的根节点,该最小不平衡树失去平衡

    比如上图LL情况,在根节点8的左子树的左子树上插入节点1或节点3,都会导致以8为根节点的树的平衡因子为2, 故8为根节点的树为最小不平衡树,则此时需要对该最小不平衡树的根节点进行LL单旋

  • RRRightRight, 也称为右右。 在某结点的右子树的右子树上插入或在某结点的左子树上删除一个节点后,导致该结点的平衡因子变为-2, 则该结点成为最小不平衡树的根节点,该最小不平衡树失去平衡

    比如上图RR情况,在根节点8的右子树的右子树上插入节点13或节点15,都会导致以8为根节点的树的平衡因子为-2, 故8为根节点的树为最小不平衡树,则此时需要对该最小不平衡树的根节点进行RR单旋

  • LRLeftRight, 也称为左右。 在某结点的左子树的右子树上插入或在某结点的右子树上删除一个节点后,导致该结点的平衡因子变为2, 则该结点成为最小不平衡树的根节点,该最小不平衡树失去平衡

    比如上图LR情况,在根节点8的左子树的右子树上插入节点5或节点7,都会导致以8为根节点的树的平衡因子为2, 故8为根节点的树为最小不平衡树,则此时需要对该最小不平衡树的根节点的左子树进行LL单旋, 然后再对根节点进行RR单旋

  • LRRightLeft, 也称为右左。 在某结点的右子树的左子树上插入或在某结点的左子树上删除一个节点后,导致该结点的平衡因子变为-2, 则该结点成为最小不平衡树的根节点,该最小不平衡树失去平衡

    比如上图RL情况,在根节点8的右子树的左子树上插入节点9或节点11,都会导致以8为根节点的树的平衡因子为-2, 故8为根节点的树为最小不平衡树,则此时需要对该最小不平衡树的根节点的右子树进行RR单旋, 然后再对根节点进行LL单旋

LL 单旋

LL失衡情况,可通过一次旋转让最小不平衡树恢复平衡,如下图,旋转之前 k2的平衡因子为2, 即k2为最小不平衡树的根节点, 然后将最小不平衡树围绕其根节点k2旋转,旋转为右图所示。

注意:虽然是顺时针向右旋转,但不可记混,此为LL单旋

《大话数据结构学习笔记 - 查找之平衡二叉树(AVL)及其C实现》

LL单旋代码
/* * LL 单旋 * 返回值: 旋转后的节点 */
AVLTNode* LL_Rotate(AVLTNode *p)
{
    AVLTNode *q = p->lchild;
    p->lchild = q->rchild;
    q->rchild = p;

    p->height = MAX(GetHeight(p->lchild), GetHeight(p->rchild)) + 1;
    q->height = MAX(GetHeight(q->lchild), GetHeight(q->rchild)) + 1;

    return q;
}

RR单旋

RR失衡情况,可通过一次旋转让最小不平衡树恢复平衡,如下图,旋转之前 k1的平衡因子为-2, 即k1为最小不平衡树的根节点, 然后将最小不平衡树围绕其根节点k1旋转,旋转为右图所示。

《大话数据结构学习笔记 - 查找之平衡二叉树(AVL)及其C实现》

注意:虽然是逆时针向左旋转,但不可记混,此为RR单旋

RR单旋代码
/* * RR 单旋 * 返回值: 旋转后的节点 */
AVLTNode* RR_Rotate(AVLTNode *p)
{
    AVLTNode *q = p->rchild;
    p->rchild = q->lchild;
    q->lchild = p;

    p->height = MAX(GetHeight(p->lchild), GetHeight(p->rchild)) + 1;
    q->height = MAX(GetHeight(q->lchild), GetHeight(q->rchild)) + 1;

    return q;
}

LR双旋

LR失衡情况, 需通过两次旋转才能让最小不平衡树恢复平衡。如下图所示,第一次旋转是围绕最小不平衡树根节点k3的左子树根节点k1进行的RR单旋, 第二次是围绕最小不平衡树根节点k3进行的LL单旋

《大话数据结构学习笔记 - 查找之平衡二叉树(AVL)及其C实现》

LR双旋代码
/* * LR双旋: 先左后右双向旋转 * 返回值: 旋转后的节点 */
AVLTNode* LR_Rotate(AVLTNode *p)
{
    p->lchild = RR_Rotate(p->lchild);
    return LL_Rotate(p);
}

RL双旋

RL失衡情况, 需通过两次旋转才能让最小不平衡树恢复平衡。如下图所示,第一次旋转是围绕最小不平衡树根节点k1的右子树根节点k3进行的LL单旋, 第二次是围绕最小不平衡树根节点k1进行的RR单旋

《大话数据结构学习笔记 - 查找之平衡二叉树(AVL)及其C实现》

RL双旋代码
/* * RL双旋: 先右后左双向旋转 * 返回值: 旋转后的结点 */
AVLTNode* RL_Rotate(AVLTNode *p)
{
    p->rchild = LL_Rotate(p->rchild);
    return RR_Rotate(p);
}

插入

插入某结点到AVL

/* * 插入节点到 AVL 树中, 并返回根节点 */
AVLTree Insert(AVLTree T, ElementType data)
{
    if(T == NULL)  /* 若树为空或找到插入值的位置 */
    {
        T = CreateNode(data);
        if(!T)
            return NULL;
    }
    else if(data < T->data)  /* 将节点插入到左子树 */
    {
        T->lchild = Insert(T->lchild, data);  /* 递归寻找插入节点位置 */
        if (GetHeight(T->lchild) - GetHeight(T->rchild) == 2)  /* 判断插入后左子树与右子树的平衡因子是否为 2 */
        {
            if (data < T->lchild->data)  /* 表示插入到第一个失衡根节点的左子树的左子树上 */
                T = LL_Rotate(T);
            else                         /* 表示插入到第一个失衡根节点的左子树的右子树上 */
                T = LR_Rotate(T);
        }
    }
    else if(data > T->data)  /* 将节点插入到右子树 */
    {
        T->rchild = Insert(T->rchild, data);  /* 递归寻找插入节点位置 */
        if (GetHeight(T->rchild) - GetHeight(T->lchild) == 2)  /* 判断插入后左子树与右子树的平衡因子是否为 -2 */
        {
            if (data > T->rchild->data)  /* 表示插入到第一个失衡根节点的右子树的右子树上 */
                T = RR_Rotate(T);
            else                         /* 表示插入到第一个失衡根节点的右子树的左子树上 */
                T = RL_Rotate(T);
        }
    }
    else
        printf("Fail to insert the same node!");

    T->height = MAX(GetHeight(T->lchild), GetHeight(T->rchild)) + 1;
    return T;
}

搜索

/* * 查找某个值, 返回该结点 */
AVLTNode* Search(AVLTree T, ElementType data)
{
    if(!T || data == T->data)
        return T;
    else if(data < T->data)
        return Search(T->lchild, data);
    else
        return Search(T->rchild, data);
}

删除

删除AVL树中某结点

/* * 删除某个值 */
AVLTree Delete(AVLTree T, ElementType data)
{
    /* 判断 T 是否为空 */
    if(T == NULL)
        return NULL;
    else if(data < T->data)  // 若删除的值小于当前结点所指向的值
    {
        T->lchild = Delete(T->lchild, data);  // 在左子树中递归查找要删除的值所在的结点
        if(GetHeight(T->rchild) - GetHeight(T->lchild) == 2)  // 删除节点后, 判断结点是否失衡, 即平衡因子是否为 -2
        {
            AVLTNode *rchild = T->rchild;
            /* 比较以当前结点右子树为根节点的子树的高度, 判断应该采用哪种旋转类型 */
            if(GetHeight(rchild->rchild) > GetHeight(rchild->lchild))
                T = RR_Rotate(T);
            else
                T = RL_Rotate(T);
        }
    }
    else if(data > T->data)  // 若删除的值小于当前结点所指向的值
    {
        T->rchild = Delete(T->rchild, data);  // 在右子树中递归查找要删除的值所在的结点
        if(GetHeight(T->lchild) - GetHeight(T->rchild) == 2)  // 删除节点后, 判断结点是否失衡, 即平衡因子是否为 2
        {
            AVLTNode *lchild = T->lchild;
            /* 比较以当前结点左子树为根节点的子树的高度, 判断应该采用哪种旋转类型 */
            if(GetHeight(lchild->lchild) > GetHeight(lchild->rchild))
                T = LL_Rotate(T);
            else
                T = LR_Rotate(T);
        }
    }
    else // 找到删除节点
    {
        if(T->lchild && T->rchild)  /* 左右子树均不空 */
        {
            // 若左子树高度高于右子树, 则选取待删除节点的前驱节点取代待删除节点
            if(GetHeight(T->lchild) > GetHeight(T->rchild))
            {
                AVLTNode *tmp = GetMax(T->lchild);
                T->data = tmp->data;
                T->lchild = Delete(T->lchild, tmp->data);
            }
            else  // 否则选取待删除节点的后继节点取代待删除节点
            {
                AVLTNode *tmp = GetMin(T->rchild);
                T->data = tmp->data;
                T->rchild = Delete(T->rchild, tmp->data);
            }
        }
        else  // 若待删除节点只有左子树或右子树, 或为叶子节点
        {
            AVLTNode *tmp = T;
            T = T->lchild != NULL ? T->lchild : T->rchild;
            free(tmp);
        }
    }
    return T;
}

测试

int arr[]= {3,2,1,4,5,6,7,16,15,14,13,12,11,10,8,9}, ilen = LENGTH(arr);
AVLTree tree = NULL;
for(int i = 0; i < ilen; i++)
    tree = Insert(tree, arr[i]);
PreOrder(tree);
InOrder(tree);

输出

== 前序遍历: 7 4 2 1 3 6 5 13 10 9 8 11 15 14 16 
== 中序遍历: 1 2 3 4 5 6 7 8 9 10 11 13 14 15 16 

《大话数据结构学习笔记 - 查找之平衡二叉树(AVL)及其C实现》

关于上述操作用到的其他函数,都在源代码中实现,故可通过查阅源代码了解

时间复杂度

  • 插入、删除和查找的时间复杂度都为 O(logn) O ( l o g n )

源代码

AVLTree

结语

平衡二叉树作为查找算法还是很重要滴,并且有了AVL树的知识后,对于后面的B-Tree以及B+Tree和红黑树都有很大的帮助。一定要仔细理解,并且在学习每个操作的过程中,动手模拟操作过程,有助于理解以及加深印象。加油, Fighting

参考

AVL树(一)之 C语言详解

大话数据结构

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