数据结构-AVL树

目录

[什么是 AVL 树](#什么是 AVL 树)

[ASL 度量查找效率](#ASL 度量查找效率)

结构体定义

平衡调整

调整类型

左旋和右旋

右旋

左旋

左、右平衡调整

左平衡调整

右平衡调整

插入数据

[模拟建立 AVL 树](#模拟建立 AVL 树)


什么是 AVL 树

二叉排序树的形状取决于数据集,当二叉树的高度越小、结构越合理,搜索的性能就越好,时间复杂度 O(log2n)。G. M. Adelson-Velsky 和 E. M. Landis 在1962年的论文《An algorithm for the organization of information》中发表了一种名为 AVL 树的数据结构,它就能很好地解决这个问题。AVL 树具有以下 2 个性质:

  1. 左子树和右子树的深度之差的绝对值不超过 1;
  2. 左子树和右子树通通都是 AVL 树。

其中为了度量左右子树的深度之差,我们引入平衡因子 (BF)"的概念。例如下面的二叉搜索树的平衡因子为:

对于一棵 AVL 树,里面的所有结点的平衡因子只能取值于:-1、0、1


ASL 度量查找效率

为了更好地理解 AVL 树,请认真观察下面 2 个二叉搜索树,我们发现第二个二叉搜索树是 AVL 树,树的高度更小,查找的比较次数也更少,效率更高。

现在计算一下该 AVL 树的 ASL:

ASL(成功):(1 + 2 × 2 + 3 × 4) ÷ 6 = 17/6

ASL(失败):4 × 8 ÷ 8 = 4

和二叉搜索树对比,发现无论是成功还是失败的 ASL,AVL 树的都较小,说明效率更高。


结构体定义

cpp 复制代码
typedef struct BiTNode
{
      int data;
      int bf;  //平衡因子
      struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;

平衡调整

调整类型

由于二叉搜索树的结点是一个一个插入的,在插入时可能会造成结点的平衡因子的绝对值超过 1。造成失衡一共有 4 种情况:RR 型、LL 型、RL 型、LR 型,如图所示:

虽然有 4 种情况,但是需要遵循的原则是一样的------在尽可能减小高度的前提下,保持二叉搜索树的性质。下面就看一下 4 种情况的调整示意图,不难发现都是遵循这个原则进行调整的。

需要注意的是,当有多个结点失衡时,需要选择最小失衡子树来调整。


左旋和右旋

右旋

右旋可以形象地理解为把最上面的结点掰下来,这种操作称之为"右旋"。右旋操作后指向新的根结点,即操作之前的根结点的左结点。

cpp 复制代码
void R_Rotate(BiTree &T)
{ 
      BiTree L;      //L 表示 T 的左子树
      L = T->lchild;    //L 指向 T 的左子树 
      T->lchild = L->rchild;      //T 的左子树更改为 T 的左子树的右子树  
      L->rchild = T;      //T 的左子树的右子树修改为 T
      T = ptr;      //根结点修改为右旋之前 T 的左子树
}

左旋

对于 RR 型的调整,可以形象地理解为把最上面的结点掰下来,这种操作称之为"左旋"。左旋操作后指向新的根结点,即操作之前的根结点的右结点。

cpp 复制代码
void L_Rotate(BiTree &T)
{ 
      BiTree R;      //R 表示 T 的右子树
      R = T->rchild;        //R 指向 T 的右子树  
      T->rchild = R->lchild;      //T 的右子树更改为 T 的右子树的左子树  
      R->lchild = T;      //T 的右子树的左子树修改为 T
      T = R;      //根结点修改为左旋之前 T 的右子树
}

左、右平衡调整

左平衡调整

这个函数为左平衡旋转,将 RL 型和 LL 型进行判断和操作。需要考虑涉及结点所连接的子树,对每个结点的 BF 都进行修正。

cpp 复制代码
void LeftBalance(BiTree &T)
{ 
    BiTree L;      //L 表示 T 的左子树
    BiTree Lr;      //Lr 表示 L 的右子树

    L = T->lchild;      //L 指向 T 的左结点 
    switch(L->bf)      //根据 T 的左子树的 BF 作相应平衡处理
    {
        case 1:          //LL 型,新结点插入在 T 的左结点的左子树
        {
            T->bf = L->bf = 0;      //修正 BF 均为 0
            R_Rotate(T);      //右旋操作
            break;
        }
        case -1:          //RL 型,新结点插入在 T 的左结点的右子树,要做双旋操作
        {
            Lr = L->rchild;       //Lr 指向示 L 的右结点
            switch(Lr->bf)      //修正 T 及其左结点的平衡因子
            { 
                case 1:            //Lr 平衡因子为 1
                {
                    T->bf = -1;
                    L->bf = 0;
                    break;
                }
                case 0:            //Lr 平衡因子为 0
                {
                    T->bf = L->bf = 0;
                    break;
                }
                case -1:            //Lr 平衡因子为 -1
                {
                    T->bf = 0;
                    L->bf = 1;
                    break;
                }
            }
            Lr->bf = 0;      //修正 Lr 平衡因子为 0
            L_Rotate(T->lchild);       //对 T 的左子树作左旋操作  
            R_Rotate(T);      //对 T 作右旋操作
            break;
        }
    }
}

右平衡调整

这个函数为右平衡旋转,将 LR 型和 RR 型进行判断和操作。需要考虑涉及结点所连接的子树,对每个结点的 BF 都进行修正。

cpp 复制代码
void RightBalance(BiTree &T)
{ 
    BiTree R;      //R 表示 T 的右子树
    BiTree Rl;      //Rl 表示 R 的左子树

    R = T->rchild;      //R 指向 T 的右结点 
    switch(R->bf)      //根据 T 的右子树的 BF 作相应平衡处理
    {
        case -1:          //RR 型,新结点插入在 T 的右结点的右子树
        {
            T->bf = R->bf = 0;      //修正 BF 均为 0
            L_Rotate(T);      //左旋操作
            break;
        }
        case 1:          //LR 型,新结点插入在 T 的右结点的左子树,要做双旋操作
        {
            Rl = R->lchild;       //Rl 指向 R 的左结点
            switch(Rl->bf)      //修正 T 及其右结点的平衡因子
            { 
                case -1:            //Rl 平衡因子为 -1
                {
                    T->bf = 1;
                    R->bf = 0;
                    break;
                }
                case 0:            //Rl 平衡因子为 0
                {
                    T->bf = R->bf = 0;
                    break;
                }
                case 1:            //Rl 平衡因子为 1
                {
                    T->bf = 0;
                    R->bf = -1;
                    break;
                }
            }
            Rl->bf = 0;      //修正 Rl 平衡因子为 0
            R_Rotate(T->rchild);       //对 T 的右子树作右旋操作  
            L_Rotate(T);      //对 T 作左旋操作
            break;
        }
    }
}

插入数据

要插入一个数据,首先需要判断这个数据是否已存在,若在 AVL 树中不存在要插入的数据,则执行插入操作。函数将通过递归找到合适的插入位置,如果在插入时出现失去平衡的情况,要进行对应的平衡旋转处理。

cpp 复制代码
bool InsertAVL(BiTree &T, int e, bool &taller) {
    // taller 表示树的高度是否发生变化
    if (T == NULL) { // 若传入的 T 为空树,将创建根结点
        T = new BiTNode;
        T->data = e;
        T->bf = 0;
        T->lchild = T->rchild = NULL;
        taller = true; // 表示树的高度是否发生变化
    } else {
        if (e == T->data) { // 和 e 有相同数据的结点已存在
            taller = false;
            return false;
        }
        if (e < T->data) { // 进入左子树搜索插入位置
            if (InsertAVL(T->lchild, e, taller) == false) { // 结点已存在
                return false;
            }
            if (taller == true) { // 数据插入 T 的左子树中并且高度发生变化
                switch (T->bf) { // 检查 T 的 BF 判断是否调整
                    case 1: // 原本左子树比右子树高,需要进行左平衡处理
                        LeftBalance(T); // 左平衡处理
                        taller = false;
                        break;
                    case 0: // 原本左、右子树等高,仍保持平衡,修正 BF
                        T->bf = 1;
                        taller = true;
                        break;
                    case -1: // 原本右子树比左子树高,仍保持平衡,修正 BF
                        T->bf = 0;
                        taller = false;
                        break;
                }
            }
        } else { // 进入右子树搜索插入位置
            if (InsertAVL(T->rchild, e, taller) == false) { // 结点已存在
                return false;
            }
            if (taller == true) { // 数据插入 T 的左子树中并且高度发生变化
                switch (T->bf) { // 检查 T 的 BF 判断是否调整
                    case 1: // 原本右子树比左子树高,仍保持平衡,修正 BF
                        T->bf = 0;
                        taller = false;
                        break;
                    case 0: // 原本左、右子树等高,仍保持平衡,修正 BF
                        T->bf = -1;
                        taller = true;
                        break;
                    case -1: // 原本右子树比左子树高,需要进行右平衡处理
                        RightBalance(T);
                        taller = false;
                        break;
                }
            }
        }
    }
    return true;
}

模拟建立 AVL 树

按照整数序列 {4,5,7,2,1,3,6} 依次插入的顺序构造相应平衡二叉树。

首先结点 4 加入 AVL 树成为根结点。

结点 5 加入 AVL 树。

结点 7 加入 AVL 树,此时结点 4 的平衡因子为 -2,需要进行调整。

进行 RR 型调整。

结点 2 加入 AVL 树。

结点 1 加入 AVL 树,此时结点 4 的平衡因子为 2,需要进行调整。

进行 LL 型调整。

结点 3 加入 AVL 树,此时结点 5 的平衡因子为 2,需要进行调整。

进行 LR 型调整。

结点 6 加入 AVL 树,此时结点 5 的平衡因子为 -2,需要进行调整。

进行 RL 型调整。

到此为止,AVL 树建立完毕。完整的代码实现如下:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

// AVL树的节点结构
typedef struct BiTNode {
    int data;
    int bf; // 平衡因子
    struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;

// 右旋转
void R_Rotate(BiTree *P) {
    BiTree L;
    L = (*P)->lchild;
    (*P)->lchild = L->rchild;
    L->rchild = (*P);
    *P = L;
}

// 左旋转
void L_Rotate(BiTree *P) {
    BiTree R;
    R = (*P)->rchild;
    (*P)->rchild = R->lchild;
    R->lchild = (*P);
    *P = R;
}

// 左平衡旋转处理
void LeftBalance(BiTree *T) {
    BiTree L, Lr;
    L = (*T)->lchild;
    switch (L->bf) {
        case 1:
            (*T)->bf = L->bf = 0;
            R_Rotate(T);
            break;
        case -1:
            Lr = L->rchild;
            switch (Lr->bf) {
                case 1:
                    (*T)->bf = -1;
                    L->bf = 0;
                    break;
                case 0:
                    (*T)->bf = L->bf = 0;
                    break;
                case -1:
                    (*T)->bf = 0;
                    L->bf = 1;
                    break;
            }
            Lr->bf = 0;
            L_Rotate(&(*T)->lchild);
            R_Rotate(T);
    }
}

// 右平衡旋转处理
void RightBalance(BiTree *T) {
    BiTree R, Rl;
    R = (*T)->rchild;
    switch (R->bf) {
        case -1:
            (*T)->bf = R->bf = 0;
            L_Rotate(T);
            break;
        case 1:
            Rl = R->lchild;
            switch (Rl->bf) {
                case 1:
                    (*T)->bf = 0;
                    R->bf = -1;
                    break;
                case 0:
                    (*T)->bf = R->bf = 0;
                    break;
                case -1:
                    (*T)->bf = 1;
                    R->bf = 0;
                    break;
            }
            Rl->bf = 0;
            R_Rotate(&(*T)->rchild);
            L_Rotate(T);
    }
}

// 插入节点到AVL树中
int InsertAVL(BiTree *T, int key, int *taller) {
    if (!*T) {
        *T = (BiTree)malloc(sizeof(BiTNode));
        (*T)->data = key;
        (*T)->lchild = (*T)->rchild = NULL;
        (*T)->bf = 0;
        *taller = 1;
    } else {
        if (key == (*T)->data) { // 树中已存在相同的节点
            *taller = 0;
            return 0;
        }
        if (key < (*T)->data) { // 插入到左子树
            if (!InsertAVL(&((*T)->lchild), key, taller))
                return 0;
            if (*taller) { // 高度增加了
                switch ((*T)->bf) {
                    case 1:
                        LeftBalance(T);
                        *taller = 0;
                        break;
                    case 0:
                        (*T)->bf = 1;
                        *taller = 1;
                        break;
                    case -1:
                        (*T)->bf = 0;
                        *taller = 0;
                        break;
                }
            }
        } else { // 插入到右子树
            if (!InsertAVL(&((*T)->rchild), key, taller))
                return 0;
            if (*taller) { // 高度增加了
                switch ((*T)->bf) {
                    case 1:
                        (*T)->bf = 0;
                        *taller = 0;
                        break;
                    case 0:
                        (*T)->bf = -1;
                        *taller = 1;
                        break;
                    case -1:
                        RightBalance(T);
                        *taller = 0;
                        break;
                }
            }
        }
    }
    return 1;
}

int main() {
    int count;      // 数据元素的个数
    BiTree T = NULL;
    int taller, flag; // taller 反映 T 的高度是否变化
    int a_num;      // 暂存输入数据

    scanf("%d", &count);
    for (int i = 0; i < count; i++) {
        scanf("%d", &a_num);
        flag = InsertAVL(&T, a_num, &taller); // 向 AVL 树中插入 a_num
    }
    return 0;
}
相关推荐
hsling松子4 小时前
使用PaddleHub智能生成,献上浓情国庆福
人工智能·算法·机器学习·语言模型·paddlepaddle
dengqingrui1235 小时前
【树形DP】AT_dp_p Independent Set 题解
c++·学习·算法·深度优先·图论·dp
C++忠实粉丝5 小时前
前缀和(8)_矩阵区域和
数据结构·c++·线性代数·算法·矩阵
我的心永远是冰冰哒5 小时前
ad.concat()学习
学习
ZZZ_O^O6 小时前
二分查找算法——寻找旋转排序数组中的最小值&点名
数据结构·c++·学习·算法·二叉树
CV-King6 小时前
opencv实战项目(三十):使用傅里叶变换进行图像边缘检测
人工智能·opencv·算法·计算机视觉
代码雕刻家6 小时前
数据结构-3.9.栈在递归中的应用
c语言·数据结构·算法
雨中rain6 小时前
算法 | 位运算(哈希思想)
算法
slomay7 小时前
关于对比学习(简单整理
经验分享·深度学习·学习·机器学习
hengzhepa7 小时前
ElasticSearch备考 -- Async search
大数据·学习·elasticsearch·搜索引擎·es