數據結構與算法 -- 平衡二叉樹的構建

數據結構與算法 -- 平衡二叉樹的構建

前言

上一篇學習了一些常見的靜態查找和動態查找中二叉搜索樹的查找,插入和刪除操作。在構建一個二叉搜索樹的時候,假如給定的數據是一直遞增的,那麼就會一直存儲在右子樹上,構成一個斜樹。這時在對其做查找時,效率一樣很低。

那麼在構建二叉搜索樹的時候,怎麼解決這樣問題呢?接下來我們介紹一下利用平衡二叉樹解決二叉搜索樹失衡的問題。

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);
    }
}

個人見解,如有錯誤,歡迎指正。

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