數據結構(六)查找---平衡二叉樹(ASL)


前提

我們之前的二叉排序樹的插入(構建)是按照我們輸入的數據來進行的,若是我們的數據分布不同,那么就會構造不同的二叉樹
{ 62, 88, 58, 47, 35, 73, 51, 99, 37, 93 }

 

{ 35, 37, 47, 51, 58, 62, 73, 88, 93, 99 }

我們發現若是數組元素分布大小按順序,那么我們極有可能得到一顆極不平衡的二叉樹,而二叉樹深度越大,查找的次數越多,其查找時間復雜度可以高達O(n),那么如何構造一顆平衡的二叉樹?

 

平衡二叉樹

一:定義

平衡:

左右均勻

平衡因子:

將二叉樹上結點的左子樹深度減去右子樹深度的值稱為平衡因子BF(Balance Factor) BF=hl-hr

平衡二叉樹(AVL樹):

是一種二叉排序樹
空樹或任一結點左右子樹高度差的絕對值不超過1,即|BF|<=1

最小不平衡子樹

距離插入結點最近的,且平衡因子絕對值大於1的結點為根 的子樹,我們稱為最小不平衡子樹

 

二:平衡二叉樹實現原理

基本思想

在構建二叉排序樹的過程中,每當插入一個結點時,先檢查是否因插入而破壞了樹的平衡性,若是,則找出最小不平衡子樹。在保存二叉排序樹的前提下,調整最小不平衡子樹中各個結點之間的鏈接更新進行相應的旋轉,使之成為新的平衡子樹

二叉排序樹構建過程

{3,2,1,4,5,6,10,9,8,7}

我們若是按照二叉排序樹進行構建 圖一

雖然會符合二叉排序樹的定義,但是高度達到8的二叉樹,查找不好,效率不高,我們應該盡可能是二叉排序樹保持平衡,比如圖二

開始構建平衡二叉樹AVL

1.選取第一個數據元素3,按照二叉排序樹方法正常構建數據,樹的平衡因子為0,符合平衡

2.選取第二個數據元素2,按照二叉排序樹方法正常構建數據,樹的平衡因子為1,符合平衡

3.選取第三個數據元素1,按照二叉排序樹方法構建數據位置,樹的根節點平衡因子為2,不符合平衡要求,我們找到最小不平衡子樹,進行旋轉

注意:平衡因子為正數,則右轉,為負數,則左轉

4.選取第四個數據元素4,按照二叉排序樹方法正常構建數據,樹的平衡因子沒改變,符合平衡

5.選取第五個數據元素5,按照二叉排序樹方法正常構建數據,結點3的BF變為-2,說明要進行旋轉,我們找到最小不平衡子樹,進行旋轉

負數,左旋

6.選取第六個數據元素6,按照二叉排序樹方法正常構建數據,發現結點2的BF變為-2,說明要進行旋轉,而且是左旋

注意:此時本來結點3是結點4的左孩子,由於旋轉后,需要滿足二叉排序樹圖像,因此我們將他變為結點2的右孩子

7.選取第七個數據元素7,按照二叉排序樹方法正常構建數據,發現結點5的BF變為-2,所以需要對這個最小不平衡子樹進行左旋

8.選取第八個數據元素10,按照二叉排序樹方法正常構建數據,樹的平衡因子沒改變,符合平衡

9.選取第八個數據元素9,按照二叉排序樹方法正常構建數據,發現結點7的BF值為-2,我們需要進行旋轉

 

注意:因為我們的結點7的BF=-2,而他的子結點10的BF是1,對於兩個符號不統一的最小不平衡子樹,
我們都應該先讓其符號相同,所以先對我們的最小不平衡子樹的子樹結點10和結點9安裝其結點10的BF=1,正數,先進行兩個結點的右旋,

然后再對整個不平衡子樹按照結點7的BF=-2進行左旋

10.選取第九個數據元素8,按照二叉排序樹方法正常構建數據,發現結點6的BF=-2,而且最小不平衡子樹的符號不統一

 

我們先對最小不平衡子樹的子樹進行旋轉,使得其符號統一,按照結點9的BF=1,進行右旋

使最小不平衡子樹符號相同,然后我們根據結點6的BF=-2,進行左旋

最后將所有的數據排序完成!!!

三:平衡二叉樹的難點

1.我們需要知道每個結點的BF值,應該從哪得知?

所以我們要在結構體中加入平衡因子數據域
typedef struct _BiTNode
{
    ElemType data;
    int bf;
    struct _BiTNode* lchild, *rchild;
}BiTNode,*BiTree;

2.我們如何動態修改每個結點的BF值?

(1)我們需要知道,我們插入一個結點,只會影響到該結點到根節點的路徑上的結點的BF值,是不會影響到其他結點的BF值

 



(2)我們插入一個新的結點,那么這個新的結點的BF值一定是0(可以看上圖)

(3)我們對一個最小不平衡子樹做了平衡處理后,會發現我們只對這個最小不平衡子樹的BF進行了改變,而對於這棵樹中的其他結點的BF值,雖然變換當中會改變,但是變化后和原來是一樣的。

下面我們對添加新結點前,添加后,樹平衡調整后的BF值進行觀察

 

這里不在最小平衡子樹中的點有0,1結點,開始和結束后其BF都沒有變化

 

這里不在最小平衡子樹中的點有1結點,開始和結束后其BF都沒有變化

這里不在最小平衡子樹中的點有1,2,3,4結點,開始和結束后其BF都沒有變化

這里不在最小平衡子樹中的點有1,2,3,4,5,6結點,開始和結束后其BF都沒有變化

 

總之:我們在考慮樹的結點的BF值時,我們只需要考慮我們的最小不平衡子樹的結點的BF值即可。

(4)同3注意:我們還發現,除了 參與旋轉的三個結點,在最小不平衡子樹的其他結點的BF值也不會改變

LL型

LR型

 

RR和RL型相同

所以:我們只需要考慮的結點是最小不平衡子樹的3個旋轉結點即可

(5)通過上面分析:我們只需要考慮3個結點的BF值變化即可,但是具體變化方式是不是有規律的?

 LL型

插入結點時的變化,我們應該將BL的BF值修改,原來是0,插入子節點后變為1

做了平衡旋轉后,我們應該將最小不平衡子樹的根節點A和左子樹根節點B變為0

LR型(我們這里只考慮插入在雙親結點左側:分多種情況,要根據第三個結點再次進行分析)

首先是T指向新插入的C結點的雙親BR由原來的0變為1,再向上走T等於其雙親,原來也是0,但是這里是右轉所以由0變為-1,之后轉到A結點,發現是左轉,原來BF值是1,直接進入左旋轉平衡

左旋轉平衡,根據LR判斷,若是為1,我們將最小不平衡子樹根T置為-1,L和LR結點設為0

分為這三種情況(想吐)....RR和RL同上面分析。

四:代碼實現

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0

typedef int Status;
typedef int ElemType;

typedef struct _BiTNode
{
    ElemType data;
    int bf;
    struct _BiTNode* lchild, *rchild;
}BiTNode,*BiTree;

//右旋操作
/*
對以p為根節點的二叉排序樹進行右旋操作
處理之后p指向新的樹根結點,即旋轉處理之前的左子樹的根節點
*/
void R_Rotate(BiTree *p)
{
    BiTree L;
    L = (*p)->lchild;
    (*p)->lchild = L->rchild;
    L->rchild = (*p);
    *p = L;
}

//左旋操作
/*
對以p為根節點的二叉排序樹進行左旋操作
處理之后p指向新的樹根結點,即旋轉處理之前的右子樹的根節點
*/
void L_Rotate(BiTree *p)
{
    BiTree R;
    R = (*p)->rchild;
    (*p)->rchild = R->lchild;
    R->lchild = (*p);
    *p = R;
}



#define LH +1    //左高
#define EH 0    //等高
#define RH -1    //右高

//左平衡旋轉處理代碼
//其中傳入的T都是最小不平衡子樹的根節點
//我們在旋轉時主要關注的BF值就是最小不平衡子樹的根節點BF和根節點下面的子節點BF值
void LeftBalance(BiTree *T)        //左平衡。我們主要考慮LL,LR兩種
{
    BiTree L,Lr;
    L = (*T)->lchild;
    switch (L->bf)    //由於已經是要做平衡處理,所以L->bf不會出現EH狀態
    {
    case LH:    //LL直接右旋即可,注意LL后的結點平衡后都是0
        (*T)->bf = L->bf = EH;    //所有的BF跳轉都是基於旋轉之前的提前調整,方便些
        R_Rotate(T);
        break;
    case RH:    //LR旋轉,我們需要注意先要左旋,然后右旋
        //在左右旋之前,我們要修改BF值
        Lr = L->rchild;    //Lr指向T的左孩子的右子樹
        switch (Lr->bf)
        { /*  修改T及其左孩子的平衡因子 */
        case LH:
            (*T)->bf = RH;
            L->bf = EH;
            break;
        case EH:
            (*T)->bf = EH;
            L->bf = EH;
            break;
        case RH:
            (*T)->bf = EH;
            L->bf = LH;
            break;
        }
        Lr->bf = EH;
        L_Rotate(&(*T)->lchild);/*  對T的左子樹作左旋平衡處理 */
        R_Rotate(T);    /*  對T作右旋平衡處理 */ 
        break;
    }
}

/*  對以指針T所指結點為根的二叉樹作右平衡旋轉處理, */
void RightBalance(BiTree* T)
{
    BiTree R, Rl;
    R = (*T)->rchild; /*  R指向T的右子樹根結點 */
    switch (R->bf)
    {/*  檢查T的右子樹的平衡度,並作相應平衡處理 */  
    case RH: /*  新結點插入在T的右孩子的右子樹上,要作單左旋處理 */
        (*T)->bf = R->bf = EH;
        L_Rotate(T);
        break;
    case LH:/*  新結點插入在T的右孩子的左子樹上,要作雙旋處理 */ 
        Rl = R->lchild;/*  Rl指向T的右孩子的左子樹根 */  
        switch (Rl->bf)
        {
        case RH:
            (*T)->bf = LH;
            R->bf = EH;
            break;
        case EH:
            (*T)->bf = EH;
            R->bf = EH;
            break;
        case LH:
            (*T)->bf = EH;
            R->bf = RH;
            break;
        }
        Rl->bf = EH;
        R_Rotate(&(*T)->rchild);/*  對T的右子樹作右旋平衡處理 */  
        L_Rotate(T);
        break;
    }
}


/*  若在平衡的二叉排序樹T中不存在和e有相同關鍵字的結點,則插入一個 */
/*  數據元素為e的新結點,並返回1,否則返回0。若因插入而使二叉排序樹 */
/*  失去平衡,則作平衡旋轉處理,布爾變量taller反映T長高與否。 */
Status InsertAVL(BiTree* T, int e, Status *taller)
{
    if (!*T)
    {
        //插入新結點
        *T = (BiTree)malloc(sizeof(BiTNode));
        (*T)->data = e;
        (*T)->lchild = (*T)->rchild = NULL;
        (*T)->bf = EH;
        *taller = TRUE;
    }
    else
    {
        if (e==(*T)->data)
        {
            //樹中已經存在和e有相同的關鍵字的結點則不再插入
            *taller = FALSE;
            return FALSE;
        }
        else if (e<(*T)->data)
        {
            //應該繼續在T的左子樹中進行搜索
            if (!InsertAVL(&(*T)->lchild, e, taller))    //未插入
                return FALSE;
            if (*taller)    //已插入到T的左子樹中,且左子樹長高
            {
                switch ((*T)->bf)    //檢測T樹的平衡度
                {
                case LH:    //原來是其父節點T的BF值為1,現在插入左孩子,其BF值變為2,直接進行左平衡處理
                    LeftBalance(T);
                    *taller = FALSE;
                    break;
                case EH:    //原來左右子樹等高,現因左子樹增高而樹增高
                    (*T)->bf = LH;
                    *taller = TRUE;
                    break;
                case RH:    //原來右子樹比左子樹高,現在左右等高
                    (*T)->bf = EH;
                    *taller = FALSE;
                    break;
                }
            }
        }
        else
        {
            //去右子樹搜索
            if (!InsertAVL(&(*T)->rchild, e, taller))    //未插入
                return FALSE;
            if (*taller)    //已插入到T的左子樹中,且左子樹長高
            {
                switch ((*T)->bf)    //檢測T樹的平衡度
                {
                case LH:    //原來左子樹比右子樹高,現在左右等高
                    (*T)->bf = EH;
                    *taller = FALSE;
                    break;
                case EH:    //原來左右子樹等高,現因右子樹增高而樹增高
                    (*T)->bf = RH;
                    *taller = TRUE;
                    break;
                case RH:    //原來右子樹比左子樹高,現在高了兩個度,BF=2,需要進行右平衡旋轉
                    RightBalance(T);
                    *taller = FALSE;
                    break;
                }
            }
        }
    }
    return TRUE;
}

int main()
{
    int i;
    int a[10] = { 3, 2, 1, 4, 5, 6, 7, 10, 9, 8 };
    BiTree T = NULL;
    Status taller;
    for (i = 0; i < 10;i++)
    {
        InsertAVL(&T, a[i], &taller);
    }
    system("pause");
    return 0;
}

 

五:反思

不太熟練,需要多聯系,等我把這些都復習一遍,在做題的時候會進行更多的查漏補缺,而且上面缺少刪除部分代碼,在我真正理解后,會補上

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM