数据结构与算法 -- 平衡二叉树的构建

数据结构与算法 -- 平衡二叉树的构建

前言

上一篇学习了一些常见的静态查找和动态查找中二叉搜索树的查找,插入和删除操作。在构建一个二叉搜索树的时候,假如给定的数据是一直递增的,那么就会一直存储在右子树上,构成一个斜树。这时在对其做查找时,效率一样很低。

那么在构建二叉搜索树的时候,怎么解决这样问题呢?接下来我们介绍一下利用平衡二叉树解决二叉搜索树失衡的问题。

1. 平衡二叉树构建分析

平衡二叉树:是一种二叉排序树,其中每一个结点的左右子树的高度差至多等于1。平衡二叉树又称为AVL树

高度平衡:要么是一个空树,要么左右子树都是平衡二叉树。且左子树和右子树的深度差绝对值不超过1;我们将二叉树上左子树的深度减去右子树的深度的值称为平衡因子BF

最小不平衡子树:距离插入点最近的,且平衡因子的绝对值大于1的结点为根的子树,我们称为最小不平衡子树

如上图中,当插入新节点37时,就会打乱原有的平衡,图中圈出来的就是最小不平衡子树。

平衡二叉树构建的基本思想:在构建二叉排序树的过程中,每当插入新结点是,先检查是否因插入而破坏了树的平衡性,若是,则找到最小不平衡子树,在保证二叉排序树特性的前提下,调整最小不平衡子树中各节点之间的链接关系,进行左旋或者右旋,使之成为最新的平衡子树

接下来,我们举个例子分析一下:

假设数组a[10] = {3,2,1,4,5,6,7,10,9,8}

在构建正常的二叉排序树,如下:

显然是失衡的,不能构成平衡二叉树

接下来,我们来模拟一下平衡二叉树

  • 首先插入结点 3,2,1的过程。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LMz4zGHC-1590137686477)(https://user-gold-cdn.xitu.io/2020/5/22/1723a3da69fa1dbc?w=761&h=356&f=png&s=22368)]

当依次插入结点3、2、1后,结点3的平衡因子是2,进行右旋。

  • 插入结点 4


插入结点4后,没有失衡

  • 插入结点 5

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0au4hsI3-1590137686492)(https://user-gold-cdn.xitu.io/2020/5/22/1723a53e8c4c4296?w=867&h=408&f=png&s=28139)]

当插入结点5时,结点3平衡因子-2,成为最小不平衡子树,进行左旋。

  • 插入结点 6

当插入结点6时,结点2平衡因子-2,成为最小不平衡子树,进行左旋。

  • 插入结点7

结点5平衡因子-2,成为最小不平衡子树,进行左旋。

  • 插入结点10

插入后没有破坏原来的平衡

  • 插入结点9

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oR5oJgVb-1590137686503)(https://user-gold-cdn.xitu.io/2020/5/22/1723a5a4ca9c683d?w=532&h=372&f=png&s=46088)]

结点7平衡因子-2,成为最小不平衡子树,进行左旋。此时结点9结点10小,不能成为结点10的右子树。

那么应该怎么办呢?

那么我们先总结一下上面的规律:

1. 平衡因子为负数,左旋
2. 平衡因子为正数,右旋
3. 平衡因子为有正负数,先旋转统一平衡因子的符号,进行双旋转

那么,我们可以先对结点10结点9,进行一次右旋,如下:

此时,平衡因子的符号统一,结点7平衡因子-2,然后再进行一次左旋,如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IQ60Oawh-1590137686508)(https://user-gold-cdn.xitu.io/2020/5/22/1723a65a2250fc8a?w=560&h=362&f=png&s=46322)]

  • 插入结点8

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r8FqclrC-1590137686510)(https://user-gold-cdn.xitu.io/2020/5/22/1723a66db2414c67?w=555&h=434&f=png&s=60500)]

当插入结点8时,出现了平衡因子符号不统一的情况,先进行右旋统一符号,得到如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n0dq8cqg-1590137686512)(https://user-gold-cdn.xitu.io/2020/5/22/1723a68ae34a55ef?w=618&h=445&f=png&s=63653)]

然后对结点6,进行左旋。

最终数组a插入完毕,得到平衡二叉树

旋转规律:

  1. 平衡因子为负数,左旋
  2. 平衡因子为正数,右旋
  3. 平衡因子为有正负数,先旋转统一平衡因子的符号,进行双旋转

2. 平衡二叉树构建

经过前面对平衡二叉树构建的分析,接下来分析一下具体怎么构建平衡二叉树

首先,我们对二叉树二叉链表结点结构,进行改造,加入平衡因子属性。

二叉树二叉链表结点结构如下:

//二叉树的二叉链表结点结构定义
//结点结构
typedef struct BiTNode{
    //结点数据
    int data;
    //结点的平衡因子
    int bf;
    //结点左右孩子指针
    struct BiTNode *lchild,*rchild;
    
}BiTNode,*BiTree;

平衡二叉树节点需要右旋操作时:

如上图,插入节点N时,破坏了原有的平衡,需要进行右旋,步骤如下:

1. 将 P 做为右旋的根节点
2. L 的右子树,即:Lr,成为 P 的左子树
3. P 成为 L 的右子树
4. L 替换原来的P,成为二叉排序树新的根节点

右旋的实现:

// 右旋
void R_Rotate(BiTree *p){
    BiTree L;
    //✅ L是p的左子树;
     L = (*p)->lchild;
    //✅ L的右子树作为p的左子树
    (*p)->lchild =  L->rchild;
    //✅ 将p作为L的右子树
     L->rchild = (*p);
    //✅ 将L替换原有p的根结点位置
    *p =  L;
    
}

平衡二叉树节点需要左旋操作时,其实和右旋是一样的思路:

1. P 作为左旋的根节点
2. R 的左子树,成为P的右子树
3. P成为R的左子树
4. R 替换 P 成为二叉树新的根节点

左旋代码实现:

// 左旋
void L_Rotate(BiTree *p){
    BiTree R;
    //✅ R是p的右子树
    R = (*p)->rchild;
    //✅ R的左子树作为R的右子树
    (*p)->rchild = R->lchild;
    //✅ 将p作为R的左子树;
    R->lchild = (*p);
    //✅ 将R替换原有p的根结点的位置
    *p = R;
}

当需要双旋操作时,又分为两种情况,分别是左子树失衡需要双旋右子树失衡需要双旋,我们可以先定义三个常量:

#define LH +1 /*  左高 */
#define EH  0  /*  等高 */
#define RH -1 /*  右高 */
  1. 左子树失衡需要双旋:

    双旋前要先统一平衡因子的符号,然后先左旋,再右旋

  2. 右子树失衡需要双旋

    双旋前要先统一平衡因子的符号,然后先右旋,再左旋

那么接下来看一下整体的平衡二叉树(AVL树) 的实现:

平衡二叉排序树T中,不存在和插入元素e有相同关键字的结点,则插入一个数据元素为e的新节点,并返回1,否则返回0。若因为插入而是二叉排序树失衡,则做平衡旋转处理。变量taller反映 平衡二叉排序树T 是否长高。

构建思路:

    1. 判断 二叉排序树 T,是否为空,为空则创建一个新节点,此时,只有一个结点,其平衡因子BFEH,表示等高,新结点,默认长高,taller = TRUE
    1. T 不为空,判断插入元素e是否等于结点T的数据,相等则返回false,默认taller = false,不长高
    1. 当插入元素e小于结点T的数据时,在T的左子树中进行搜索:
    • 3.1 先递归判断是否成功插入,未插入,直接返回 false

    • 3.2 判断成功插入到T的左子树中,且左子树长高时:

    • 3.3 检查T的平衡度:

      • TBFLH(左高)时,原本左子树比右子树高,需要做左平衡处理,此时taller = false
      • TBFEH(等高)时,原本左子树和右子树等高,左子树增高而使树增高,此时taller = trueT的BFLH
      • TBFRH(右高)时,原本右子树比左子树高,左子树增高而使左右子树等高,此时taller = falseT的BFEH
    1. 当插入元素e大于结点T的数据时,在T的右子树中进行搜索:
    • 4.1 先递归判断是否成功插入,未插入,直接返回 false

    • 4.2 判断成功插入到T的左子树中,且右子树长高时:

    • 4.3 检查T的平衡度:

      • TBFLH(左高)时,原本左子树比右子树高,右子树增高而使左右子树等高,此时taller = falseT的BFEH
      • TBFEH(等高)时,原本左子树和右子树等高,右子树增高而使树增高,此时taller = trueT的BFRH
      • TBFRH(右高)时,原本右子树比左子树高,右子树增高而使树失衡,需要做右平衡处理,此时taller = false

左平衡处理思路:

  • 检查T的左子树的平衡度L = T->lchild,若其左子树L的平衡度为LH,新结点插入在T的左孩子L左子树上,要作单右旋处理(右旋上面已经分析过)

  • T的左子树的平衡度为RH,即:L->bf = RH时,需要双旋处理Lr指向T的左孩子的右子树根,即:Lr=L->rchild,修改T及其左孩子的平衡因子

    • LrBFLH(左高)时,第一次左旋后,L平衡,修改其BFEH(等高),由于T之前是平衡的,进行旋转后修改TBFRH
    • LrBFEH(等高)时,第一次左旋后,L平衡,修改其BFEH(等高),修改TBFEH
    • LrBFRH(右高)时,第一次左旋后,L平衡,修改其BFLH(左高),修改TBFEH
  • 修改LrBFEH

  • T的左子树做左旋处理

  • T做右旋处理

右平衡处理思路:

  • 检查T的右子树的平衡度R = T->rchild,若其右子树R的平衡度为RH,新结点插入在T的右孩子R右子树上,要作单左旋处理(左旋上面已经分析过)

  • T的右子树的平衡度为LH,即:R->bf = LH时,需要双旋处理Rl指向T的右孩子的左子树,即:Rl=R->lchild,修改T及其右孩子的平衡因子

    • RlBFLH(左高)时,第一次右旋后,R平衡,修改其BFRH(等高),由于T之前是平衡的,进行旋转后修改TBFEH
    • RlBFEH(等高)时,第一次右旋后,R平衡,修改其BFEH(等高),修改TBFEH
    • RlBFRH(右高)时,第一次右旋后,R平衡,修改其BFEH(等高),修改TBFLH
  • 修改RlBFEH

  • T的右子树做右旋处理

  • T做左旋处理

Status InsertAVL(BiTree *T,int e,Status *taller)
{
    if(!*T)
    {   //1.插入新结点,树“长高”,置taller为TRUE
        //① 开辟一个新结点T;
        *T=(BiTree)malloc(sizeof(BiTNode));
        //② 对新结点T的data赋值,并且让其左右孩子指向为空,T的BF值为EH;
        (*T)->data=e;
        (*T)->lchild=(*T)->rchild=NULL;
        (*T)->bf=EH;
        //③ 新结点默认"长高"
        *taller=TRUE;
    }
    else
    {
        if (e==(*T)->data)
        {  //2.树中已存在和e有相同关键字的结点则不再插入
            *taller=FALSE;
            return FALSE;
        }
        if (e<(*T)->data)
        {
           //3.应继续在T的左子树中进行搜索
            if(!InsertAVL(&(*T)->lchild,e,taller))
                //未插入
                return FALSE;
            
            //4.已插入到T的左子树中且左子树“长高”
            if(*taller)
                //5.检查T的平衡度
                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
        { //6.应继续在T的右子树中进行搜索
            //未插入
            if(!InsertAVL(&(*T)->rchild,e,taller))
                return FALSE;
            //已插入到T的右子树且右子树“长高”
            if(*taller)
                // 检查T的平衡度
                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 TRUE;
}

 //  左平衡树失衡处理
void LeftBalance(BiTree *T)
{
    BiTree L,Lr;
    
    //1.L指向T的左子树根结点
    L=(*T)->lchild;
    
    //2.检查T的左子树的平衡度,并作相应平衡处理
    switch(L->bf)
    {
        //① 新结点插入在T的左孩子的左子树上,要作单右旋处理(如图1-平衡二叉树右旋解释图)
        case LH:
            //L的平衡因子为LH,即为1时,表示它与根结点BF符合相同,则将它们(T,L)的BF值都改为EH(0)
            (*T)->bf=L->bf=EH;
            //对最小不平衡子树T进行右旋;
            R_Rotate(T);
            break;
            
        //② LH的平衡因子为RH(-1)时,它与跟结点的BF值符合相反.此时需要做双旋处理(2次旋转处理)
        //   新结点插入在T的左孩子的右子树上,要作 双旋处理
        case RH:
            
            //Lr指向T的左孩子的右子树根
            Lr=L->rchild;
            
            //修改T及其左孩子的平衡因子
            switch(Lr->bf)
            {
            
                case LH:
                    (*T)->bf=RH;
                    L->bf=EH;
                    break;
                    
                case EH:
                    (*T)->bf=L->bf=EH;
                    break;
                    
                case RH:
                    (*T)->bf=EH;
                    L->bf=LH;
                    break;
             }
            Lr->bf=EH;
            //对T的左子树作左旋平衡处理
            L_Rotate(&(*T)->lchild);
            //对T作右旋平衡处理
            R_Rotate(T);
    }
}


 //  右平衡树失衡处理
void RightBalance(BiTree *T)
{
    BiTree R,Rl;
    //1.R指向T的右子树根结点
    R=(*T)->rchild;
    
    //2. 检查T的右子树的平衡度,并作相应平衡处理
    switch(R->bf)
    {
        //① 新结点插入在T的右孩子的右子树上,要作单左旋处理
        case RH:
            (*T)->bf=R->bf=EH;
            L_Rotate(T);
            break;
        //新结点插入在T的右孩子的左子树上,要作双旋处理
        case LH:
            //Rl指向T的右孩子的左子树根
            Rl=R->lchild;
           
            //修改T及其右孩子的平衡因子
            switch(Rl->bf)
                {
                    case RH:
                        (*T)->bf=LH;
                        R->bf=EH;
                        break;
                    case EH:
                        (*T)->bf=R->bf=EH;
                        break;
                    case LH:
                        (*T)->bf=EH;
                        R->bf=RH;
                        break;
                }
            
            Rl->bf=EH;
            //对T的右子树作右旋平衡处理
            R_Rotate(&(*T)->rchild);
            //对T作左旋平衡处理
            L_Rotate(T);
    }
}

个人见解,如有错误,欢迎指正。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章