数据结构:平衡二叉树

平衡二叉树

平衡二叉树,又称为AVL树。实际上就是遵循以下两个特点的二叉树:

  • 每棵子树中的左子树和右子树的深度差不能超过 1;
  • 二叉树中每棵子树都要求是平衡二叉树;

其实就是在二叉树的基础上,若树中每棵子树都满足其左子树和右子树的深度差都不超过 1,则这棵二叉树就是平衡二叉树。

在这里插入图片描述

图 1 平衡与不平衡的二叉树及结点的平衡因子

平衡因子:每个结点都有其各自的平衡因子,表示的就是其左子树深度同右子树深度的差。平衡二叉树中各结点平衡因子的取值只可能是:0、1和-1。

如图1所示,其中 (a) 的两棵二叉树中由于各个结点的平衡因子数的绝对值都不超过1,所以 (a) 中两棵二叉树都是平衡二叉树;而 (b) 的两棵二叉树中有结点的平衡因子数的绝对值超过1,所以都不是平衡二叉树。

二叉排序树转化为平衡二叉树

为了排除动态查找表中不同的数据排列方式对算法性能的影响,需要考虑在不会破坏二叉排序树本身结构的前提下,将二叉排序树转化为平衡二叉树。

例如,在对查找表{13,24,37,90,53}构建二叉排序树时,当插入13和24时,二叉排序树此时还是平衡二叉树:

在这里插入图片描述

图 2 平衡二叉树

当继续插入37时,生成的二叉排序树如图3(a),平衡二叉树的结构被破坏,此时只需要对二叉排序树做“旋转”操作(如图3(b)),即整棵树以结点24为根结点,二叉排序树的结构没有破坏,同时将该树转化为了平衡二叉树:

在这里插入图片描述

图 3 二叉排序树变为平衡二叉树的过程

当二叉排序树的平衡性被打破时,就如同扁担的两头出现了一头重一头轻的现象,如图3(a)所示,此时只需要改变扁担的支撑点(树的树根),就能使其重新归为平衡。实际上图 3 中的 (b) 是对(a) 的二叉树做了一个向左逆时针旋转的操作。

继续插入90和53后,二叉排序树如图4(a)所示,导致二叉树中结点24和37的平衡因子的绝对值大于1,整棵树的平衡被打破。此时,需要做两步操作:

  1. 如图4(b)所示,将结点53和90整体向右顺时针旋转,使本该以90为根结点的子树改为以结点53为根结点;

  2. 如图4(c)所示,将以结点37为根结点的子树向左逆时针旋转,使本该以37为根结点的子树,改为以结点53为根结点; 图 4 二叉排序树转化为平衡二叉树

    在这里插入图片描述

做完以上操作,即完成了由不平衡的二叉排序树转变为平衡二叉树。

当平衡二叉树由于新增数据元素导致整棵树的平衡遭到破坏时,就需要根据实际情况做出适当的调整,假设距离插入结点最近的“不平衡因子”为 a。则调整的规律可归纳为以下4种情况:

  • 单向右旋平衡处理: 图 5 单向右旋

    若由于结点a的左子树为根结点的左子树上插入结点,导致结点a的平衡因子由1增至2,致使以a为根结点的子树失去平衡,则只需进行一次向右的顺时针旋转,如下图这种情况:

    在这里插入图片描述

  • 单向左旋平衡处理:

    如果由于结点a的右子树为根结点的右子树上插入结点,导致结点a的平衡因子由-1变为-2,则以a为根结点的子树需要进行一次向左的逆时针旋转,如下图这种情况:

    在这里插入图片描述

  • 双向旋转(先左后右)平衡处理: 图 7 双向旋转(先左后右)

    如果由于结点a的左子树为根结点的右子树上插入结点,导致结点a平衡因子由1增至2,致使以a为根结点的子树失去平衡,则需要进行两次旋转操作,如下图这种情况:

    在这里插入图片描述

注意:图 7 中插入结点也可以为结点C的右孩子,则(b)中插入结点的位置还是结点C右孩子,(c)中插入结点的位置为结点A的左孩子。

  • 双向旋转(先右后左)平衡处理: 图 8 双向旋转(先右后左)

    如果由于结点a的右子树为根结点的左子树上插入结点,导致结点a平衡因子由-1变为-2,致使以a为根结点的子树失去平衡,则需要进行两次旋转(先右旋后左旋)操作,如下图这种情况:

    在这里插入图片描述

    注意:图8中插入结点也可以为结点C的右孩子,则(b)中插入结点的位置改为结点B的左孩子,(c)中插入结点的位置为结点B的左孩子。

在对查找表{13,24,37,90,53}构建平衡二叉树时,由于符合第4条的规律,所以进行先右旋后左旋的处理,最终由不平衡的二叉排序树转变为平衡二叉树。

构建平衡二叉树的代码实现

//分别定义平衡因子
#define LH +1
#define EH 0
#define RH -1

typedef struct BSTNode{
    int data;
    int bf;
    BSTNode *lchild, *rchild;
}BSTNode, *BSTree;
//对以T为根结点的二叉树做右旋处理,令T指针指向新的根结点

void R_Rotate(BSTree *T){
    BSTree lc = (*T)->lchild;
    (*T)->lchild = lc->rchild;
    lc->rchild = *T;
    *T = lc;
}
//对以T为根结点的二叉树做左旋处理,令T指针指向新的根结点
void L_Rotate(BSTree *T){
    BSTree rc = (*T)->rchild;
    (*T)->rchild = rc->lchild;
    rc->lchild = *T;
    *T = rc;
}
//对以指针 T 所指向结点为根结点的二叉树作左子树的平衡处理,令指针 T 指向新的根结点
void LeftBalance(BSTree *T){
    BSTree lc, rd;
    lc = (*T)->lchild;
    //查看以 T 的左子树为根结点的子树,失去平衡的原因,如果 bf 值为 1 ,则说明添加在左子树为根结点的左子树中,需要对其进行右旋处理;反之,如果 bf 值为 -1,说明添加在以左子树为根结点的右子树中,需要进行双向先左旋后右旋的处理
    switch (lc->bf) {
        case LH:
            (*T)->bf = lc->bf = EH;
            R_Rotate(T);
            break;
        case RH:
            rd = lc->rchild;
            switch (rd->bf) {
                case LH:
                    (*T)->bf = RH;
                    lc->bf = EH;
                    break;
                case EH:
                    (*T)->bf = lc->bf = EH;
                    break;
                case RH:
                    (*T)->bf = EH;
                    lc->bf = LH;
                    break;
            }
            rd->bf = EH;
            L_Rotate(&(*T)->lchild);
            R_Rotate(T);
            break;
    }
}
//右子树的平衡处理同左子树的平衡处理完全类似
void RightBalance(BSTree* T)
{
    BSTree lc,rd;
    lc= (*T)->rchild;
    switch (lc->bf)
    {
        case RH:
            (*T)->bf = lc->bf = EH;
            L_Rotate(T);
            break;
        case LH:
            rd = lc->lchild;
            switch(rd->bf)
            {
                case LH:
                    (*T)->bf = EH;
                    lc->bf = RH;
                    break;
                case EH:
                    (*T)->bf = lc->bf = EH;
                    break;
                case RH:
                    (*T)->bf = EH;
                    lc->bf = LH;
                    break;
            }
            rd->bf = EH;
            R_Rotate(&(*T)->rchild);
            L_Rotate(T);
            break;
    }
}

int InsertAVL(BSTree *T, int key, bool *taller){
    if ((*T) == NULL){//如果本身为空树,则直接添加key为根结点
        (*T) = new BSTNode;
        (*T)->bf = EH;
        (*T)->lchild = NULL;
        (*T)->rchild = NULL;
        (*T)->data = key;
        *taller = true;
    }
    else if ((*T)->data == key){//若二叉排序树中存在关键字key,则不做任何处理
        *taller = false;
        return 0;
    }
    else if ((*T)->data > key){//若关键字小于结点T的数据域,则插入到T的左子树
        if (!InsertAVL(&(*T)->lchild, key, taller)) return 0;//若插入不影响树的平衡,则直接结束
        if (*taller){
            //判断根结点 T 的平衡因子是多少,由于是在其左子树添加新结点的过程中导致失去平衡,所以当 T 结点的平衡因子本身为 1 时,需要进行左子树的平衡处理,否则更新树中各结点的平衡因子数
            switch ((*T)->bf) {
                case LH:
                    LeftBalance(T);
                    *taller = false;
                    break;
                case EH:
                    (*T)->bf = LH;
                    *taller = true;
                    break;
                case RH:
                    (*T)->bf =EH;
                    *taller = false;
                    break;
            }
        }
    }
    else {
        //同样,当 key>T->data 时,需要插入到以 T 为根结点的树的右子树中,同样需要做和以上同样的操作
        if (!InsertAVL(&(*T)->rchild, key, taller)) return 0;
        if (*taller){
            switch ((*T)->bf) {
                case LH:
                    (*T)->bf = EH;
                    *taller = false;
                    break;
                case EH:
                    (*T)->bf = RH;
                    *taller = true;
                    break;
                case RH:
                    RightBalance(T);
                    *taller = false;
                    break;
            }
        }
    }
    return 1;
}
//判断现有平衡二叉树中是否依据具有关键字key的结点
bool FindNode(BSTree T, int key, BSTree *pos){
    BSTree p = T;
    (*pos) = NULL;
    while (p){
        if (p->data == key){
            (*pos) = p;
            return true;
        }
        else if (p->data > key) p = p->lchild;
        else p = p->rchild;
    }
    return false;
}
//中序遍历
void InorderTra(BSTree T){
    if (T->lchild) InorderTra(T->lchild);
    cout << T->data << " ";
    if (T->rchild) InorderTra(T->rchild);
}

int main(){
    int a[9] = {1, 23, 45, 34, 98, 9, 4, 35, 23};
    BSTree root = NULL, pos;
    bool taller;
    for (int i = 0; i < 9; i++){
        InsertAVL(&root, a[i], &taller);
    }
    if (FindNode(root, 45, &pos)) cout << pos->data << endl;
    InorderTra(root);
    return 0;
}

总结

使用平衡二叉树进行查找操作的时间复杂度为O(logn)。

在平衡失衡后,不管如何旋转其目的都是为了1.降低高度;2.保持二叉树的性质。所以我们可以将三个结点的关键字进行“比较”中间值为根结点,最小值为左子树,最大值为右子树,如图所示。

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 二叉树是一种树形数据结构,它由一个根节点和最多两个子树组成,这两个子树被称为左子树和右子树。二叉树中的每个节点最多有两个子节点,如果一个节点只有一个子节点,那么这个子节点必须是左子节点。 二叉树有很多种类型,最基本的二叉树是二叉搜索树。在二叉搜索树中,左子树的所有节点的值都小于根节点的值,右子树的所有节点的值都大于根节点的值。这使得在二叉搜索树中进行查找、插入和删除操作非常高效。 除了二叉搜索树,还有平衡二叉树、红黑树、B树等多种二叉树类型,每种类型的二叉树都有其特定的应用场景和优缺点。 二叉树的遍历方式有三种:前序遍历(先访问根节点,然后遍历左子树和右子树)、中序遍历(先遍历左子树,然后访问根节点,最后遍历右子树)和后序遍历(先遍历左子树和右子树,最后访问根节点)。二叉树的遍历方式是解决很多问题的基础,比如查找二叉树中的最大值、计算二叉树的深度等。 ### 回答2: 二叉树是一种重要的数据结构,它由一组称为节点的元素组成,每个节点最多可以连接到两个子节点,分别称为左子节点和右子节点。二叉树的一个节点可以表示一个值或者一条数据。 二叉树具有以下特点: 1. 根节点:二叉树的顶部节点称为根节点,它是整个树的起点。 2. 叶子节点:没有子节点的节点称为叶子节点,它们位于树的末端。 3. 分支节点:有子节点的节点称为分支节点,它们是树的中间节点。 4. 子树:以某个节点为根节点,将其及其后代节点组成的树称为子树。 5. 左子树和右子树:一个节点的左右子节点分别构成左子树和右子树。 6. 高度:树中节点的最大层次称为树的高度。 二叉树有多种变种,如满二叉树、完全二叉树等。满二叉树是一种每个节点都有两个子节点的二叉树,而完全二叉树是一种除了最后一层外,其他层都是满的二叉树。 二叉树的应用十分广泛,常见的应用场景包括文件系统、数据库索引等。在二叉树中,插入、删除、查找等操作效率很高,时间复杂度通常为O(logN)。然而,如果二叉树退化成链表,操作效率会大大降低,时间复杂度为O(N)。 总的来说,二叉树是一种简单但十分重要的数据结构。它能够高效地存储、操作数据,被广泛应用于各个领域。 ### 回答3: 二叉树是一种常见的数据结构,它由节点组成,每个节点最多有两个子节点。 二叉树的特点是左子节点小于父节点,而右子节点大于父节点,这样的特性方便在树中进行排序和搜索操作。 二叉树有多种常见的类型,包括满二叉树、完全二叉树和平衡二叉树等。 满二叉树是指除了叶子节点外,每个节点都有两个子节点的二叉树。 完全二叉树是指除了最后一层以外的其他层都是满的,最后一层的节点从左到右依次填满。 平衡二叉树是指左子树和右子树的高度差不超过1的二叉树,这样可以保证在最坏情况下的搜索时间复杂度为O(logn)。 二叉树可以使用数组或链表实现,具体选择取决于应用场景和需求。 在二叉树中,我们可以使用递归或迭代的方式进行遍历操作,包括先序遍历、中序遍历和后序遍历。 先序遍历是指先访问根节点,然后递归遍历左子树和右子树。 中序遍历是指先递归遍历左子树,然后访问根节点,最后递归遍历右子树。 后序遍历是指先递归遍历左子树和右子树,然后访问根节点。 二叉树还可以进行插入、删除和查找操作。插入操作一般按照二叉搜索树的规则进行,即比根节点小的值插入左子树,比根节点大的值插入右子树。删除操作需要考虑不同情况,包括删除叶子节点、删除有一个子节点的节点和删除有两个子节点的节点。查找操作可以根据二叉搜索树的性质进行递归或迭代实现。 总之,二叉树是一种常见且重要的数据结构,能够方便地进行排序、搜索和插入等操作,同时还有多种类型和遍历方式供选择。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星*湖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值