淺談數據結構-平衡二叉樹


平衡二叉樹(Balanced Binary Tree)是二叉查找樹的一個進化體,也是第一個引入平衡概念的二叉樹。1962年,G.M. Adelson-Velsky 和 E.M. Landis發明了這棵樹,所以它又叫AVL樹。平衡二叉樹要求對於每一個節點來說,它的左右子樹的高度之差不能超過1,如果插入或者刪除一個節點使得高度之差大於1,就要進行節點之間的旋轉,將二叉樹重新維持在一個平衡狀態。這個方案很好的解決了二叉查找樹退化成鏈表的問題,把插入,查找,刪除的時間復雜度最好情況和最壞情況都維持在O(logN)。但是頻繁旋轉會使插入和刪除犧牲掉O(logN)左右的時間,不過相對二叉查找樹來說,時間上穩定了很多。

平衡二叉樹實現的大部分過程和二叉查找樹是一樣的(學平衡二叉樹之前一定要會二叉查找樹),區別就在於插入和刪除之后要寫一個旋轉算法去維持平衡,維持平衡需要借助一個節點高度的屬性。

一、定義及原理

現在又a[10] = {3, 2, 1, 4, 5, 6, 7, 10, 9, 8}需要構建二叉排序樹。在沒有學習平衡二叉樹之前,根據二叉排序樹的特性,通常會將它構建成如下左圖。雖然完全符合二叉排序樹的定義,但是對這樣高度達到8的二叉樹來說,查找是非常不利的。因此,更加期望構建出如下右圖的樣子,高度為4的二叉排序樹,這樣才可以提供高效的查找效率。

平衡二叉樹是一種二叉排序樹,是一種高度平衡的二叉樹,其中每個結點的左子樹和右子樹的高度至多等於1.意味着:要么是一棵空樹,要么左右都是平衡二叉樹,且左子樹和右子樹深度之絕對值不超過1. 將二叉樹上結點的左子樹深度減去右子樹深度的值稱為平衡因子BF,那么平衡二叉樹上的所有結點的平衡因子只可能是-1、0和1。只要二叉樹上有一個結點的平衡因子的絕對值大於1,則該二叉樹就是不平衡的。

平衡二叉樹的前提是它是一棵二叉排序樹。

    距離插入結點最近的,且平衡因子的絕對值大於1的結點為根的子樹,稱為最小不平衡子樹。如下圖所示,當插入結點37時,距離它最近的平衡因子的絕對值超過1的結點是58。

 

typedef struct BitNode
{
      int data;
      int bf;
      struct BitNode *lchild, *rchild;
}BitNode, *BiTree;

二、結點插入

1、插入原理

根據二叉平衡樹的定義,一定保持左右子樹深度絕對值小於1.在平衡二叉樹插入工作一定考慮深度差,在AVL樹進行插入工作時候,困難在於可能破壞AVL樹的平衡屬性。例如在下圖

 

上圖中插入一個節點6,那么如果不進行后續處理就會破壞樹的平衡性。因為8的左子樹深度為1,而右子樹深度為-1.

針對此類問題,需要根據樹的實際結構進行幾種簡單的旋轉(rotation)操作就可以讓樹恢復AVL樹的平衡性質

2.旋轉問題

對於一個平衡的節點,由於任意節點最多有兩個兒子,因此高度不平衡時,此節點的兩顆子樹的高度差2.容易看出,這種不平衡出現在下面四種情況:

1、6節點的左子樹3節點高度比右子樹7節點大2,左子樹3節點的左子樹1節點高度大於右子樹4節點,這種情況成為左左。

2、6節點的左子樹2節點高度比右子樹7節點大2,左子樹2節點的左子樹1節點高度小於右子樹4節點,這種情況成為左右。

3、2節點的左子樹1節點高度比右子樹5節點小2,右子樹5節點的左子樹3節點高度大於右子樹6節點,這種情況成為右左。

4、2節點的左子樹1節點高度比右子樹4節點小2,右子樹4節點的左子樹3節點高度小於右子樹6節點,這種情況成為右右。

從圖2中可以可以看出,1和4兩種情況是對稱的,這兩種情況的旋轉算法是一致的,只需要經過一次旋轉就可以達到目標,我們稱之為單旋轉。2和3兩種情況也是對稱的,這兩種情況的旋轉算法也是一致的,需要進行兩次旋轉,我們稱之為雙旋轉。

3、旋轉操作

單旋轉是針對於左左和右右這兩種情況的解決方案,這兩種情況是對稱的,只要解決了左左這種情況,右右就很好辦了。圖3是左左情況的解決方案,節點k2不滿足平衡特性,因為它的左子樹k1比右子樹Z深2層,而且k1子樹中,更深的一層的是k1的左子樹X子樹,所以屬於左左情況。

為使樹恢復平衡,我們把k2變成這棵樹的根節點,因為k2大於k1,把k2置於k1的右子樹上,而原本在k1右子樹的Y大於k1,小於k2,就把Y置於k2的左子樹上,這樣既滿足了二叉查找樹的性質,又滿足了平衡二叉樹的性質。

這樣的操作只需要一部分指針改變,結果我們得到另外一顆二叉查找樹,它是一棵AVL樹,因為X向上一移動了一層,Y還停留在原來的層面上,Z向下移動了一層。整棵樹的新高度和之前沒有在左子樹上插入的高度相同,插入操作使得X高度長高了。因此,由於這顆子樹高度沒有變化,所以通往根節點的路徑就不需要繼續旋轉了。

右旋轉代碼

void R_rotate(BiTree *t)
{
         BiTree s;
         s = (*t)->lchild;                    //s指向t的左子樹根結點
         (*t)->lchild = s->rchild;          //s的右子樹掛接為t的左子樹
         s->rchild = (*t);
         *t = s;                                //t指向新的根結點
}

右旋轉原理:獲取失去平衡結點以及左結點,為了讓lchild作為根節點,將lchild的rchild掛接到之前左結點上,然后在掛接到s->rchild.

左旋轉代碼

void L_rotate(BiTree *t)
{
         BiTree s;
         s = (*t)->rchild;                    //s指向t的右子樹根結點
         (*t)->rchild = s->lchild;          //s的左子樹掛接為t的右子樹
         s->lchild = (*t);
         *t = s;                                //t指向新的根結點
}

左旋轉原理正好相反,讓其右結點作為根節點

第六步:雙旋轉

對於左右和右左這兩種情況,單旋轉不能使它達到一個平衡狀態,要經過兩次旋轉。雙旋轉是針對於這兩種情況的解決方案,同樣的,這樣兩種情況也是對稱的,只要解決了左右這種情況,右左就很好辦了。圖4是左右情況的解決方案,節點k3不滿足平衡特性,因為它的左子樹k1比右子樹Z深2層,而且k1子樹中,更深的一層的是k1的右子樹k2子樹,所以屬於左右情況。

  為使樹恢復平衡,我們需要進行兩步,第一步,把k1作為根,進行一次z左旋轉,旋轉之后就變成了左左情況,所以第二步再進行一次右旋轉,最后得到了一棵以k2為根的平衡二叉樹樹。

3、旋轉代碼分析

#define LH +1 /*  左高 */ 
#define EH 0  /*  等高 */ 
#define RH -1 /*  右高 */ 
 
/*  對以指針T所指結點為根的二叉樹作左平衡旋轉處理 */
/*  本算法結束時,指針T指向新的根結點 */
void LeftBalance(BiTree *T)
{ 
    BiTree L,Lr;
    L = (*T)->lchild;                                      /*  L指向T的左子樹根結點 */
    switch(L->bf)
    { 
        /* 檢查T的左子樹的平衡度,並作相應平衡處理 */
         case LH:                   /* 新結點插入在T的左孩子的左子樹上,要作單右旋處理 */
            (*T)->bf=L->bf=EH;
            R_Rotate(T);
            break;
         case RH:                   /* 新結點插入在T的左孩子的右子樹上,要作雙旋處理 */
            Lr=L->rchild;                    /* Lr指向T的左孩子的右子樹根 */
            switch(Lr->bf)
            {   /* 修改T及其左孩子的平衡因子 */
                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;
            L_Rotate(&(*T)->lchild);         /* 對T的左子樹作左旋平衡處理 */
            R_Rotate(T);             /* 對T作右旋平衡處理 */
    }
}

首先,定義三個常數變量,分別代碼1、0、-1。

    (1)函數被調用,傳入一個需調整平衡型的子樹T,根節點為k3,由於LeftBalance函數被調用時,其實是已經確認當前子樹是不平衡的狀態,且左子樹的高度大於右子樹的高度。換句話說,此時T的根結點應該是平衡因子BF的值大於1的數。k3的BF為2

    (2)將T的左孩子賦值給L。L指向K1.

    (3)然后是分支判斷。

    (4)當L(k1)的平衡因子為LH,即為1時,表明它與根結點的BF值符號相同,因此,將它們的BF值都改為0,並進行右旋(順時針)操作,是左左情況

    (5)當L的平衡因子為RH時,即為-1時,表明它與根結點的BF值符號相反,此時需要做雙旋操作。針對L的右孩子k2的BF作判斷,修改結點T(k3)和L(k1)的BF值。將當前的Lr的BF改為0。從圖中看到K2的左結點是連接到K1的右子樹上,右結點連接到K3的左子樹

其中當k2結點為RH,說明K2有右結點有,左結點無,k3為0((*T)->bf=EH; ),k1就沒有右結點為LH。當為Lh看程序。

    (6)對根結點的左子樹進行左旋,以K1為根節點進行左旋轉,形成左左情況。

    (7)對根結點K3進行右旋,完成平衡操作。

 

三、總體代碼

<strong>#include <stdio.h>    
#include <stdlib.h>   
 
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 100                     /* 存儲空間初始分配量 */
 
typedef int Status;                     /* Status是函數的類型,其值是函數結果狀態代碼,如OK等 */
 
 
/* 二叉樹的二叉鏈表結點結構定義 */
typedef  struct BitNode                 /* 結點結構 */
{
    int data;                           /* 結點數據 */
    int bf;                             /*  結點的平衡因子 */
    struct BitNode *lchild, *rchild;    /* 左右孩子指針 */
} BitNode, *BiTree;
 
 
/* 對以p為根的二叉排序樹作右旋處理 */
/* 處理之后p指向新的樹根結點,即旋轉處理之前的左子樹的根結點 */
//右旋-順時針旋轉(如LL型就得對根結點做該旋轉)
void R_Rotate(BiTree *P)
{ 
    BiTree L;
    L=(*P)->lchild;                      /*  L指向P的左子樹根結點 */
    (*P)->lchild=L->rchild;               /*  L的右子樹掛接為P的左子樹 */
    L->rchild=(*P);
    *P=L;                               /*  P指向新的根結點 */
}
 
/* 對以P為根的二叉排序樹作左旋處理, */
/* 處理之后P指向新的樹根結點,即旋轉處理之前的右子樹的根結點0  */
//左旋-逆時針旋轉(如RR型就得對根結點做該旋轉)
void L_Rotate(BiTree *P)
{ 
    BiTree R;
    R = (*P)->rchild;                    /* R指向P的右子樹根結點 */
    (*P)->rchild = R->lchild;         /* R的左子樹掛接為P的右子樹 */
    R->lchild = (*P);
    *P = R;                             /* P指向新的根結點 */
}
 
#define LH +1                           /*  左高 */ 
#define EH 0                            /*  等高 */ 
#define RH -1                           /*  右高 */ 
 
/*  對以指針T所指結點為根的二叉樹作左平衡旋轉處理 */
/*  本算法結束時,指針T指向新的根結點 */
void LeftBalance(BiTree *T)
{ 
    BiTree L,Lr;
    L = (*T)->lchild;                    /*  L指向T的左子樹根結點 */
    switch(L->bf)
    { 
        /* 檢查T的左子樹的平衡度,並作相應平衡處理 */
        case LH:                        /* 新結點插入在T的左孩子的左子樹上,要作單右旋處理 */
            (*T)->bf=L->bf=EH;
            R_Rotate(T);
            break;
        case RH:                        /* 新結點插入在T的左孩子的右子樹上,要作雙旋處理 */ //
            Lr=L->rchild;                /* Lr指向T的左孩子的右子樹根 */
            switch(Lr->bf)
            {   
                /* 修改T及其左孩子的平衡因子 */
                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;
            L_Rotate(&(*T)->lchild); /* 對T的左子樹作左旋平衡處理 */
            R_Rotate(T);                /* 對T作右旋平衡處理 */
    }
}
 
/*  對以指針T所指結點為根的二叉樹作右平衡旋轉處理, */
/*  本算法結束時,指針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)
            { 
                /*  修改T及其右孩子的平衡因子 */
                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;
            R_Rotate(&(*T)->rchild); /*  對T的右子樹作右旋平衡處理 */
            L_Rotate(T);                /*  對T作左旋平衡處理 */
    }
}
 
/*  若在平衡的二叉排序樹T中不存在和e有相同關鍵字的結點,則插入一個 */
/*  數據元素為e的新結點,並返回1,否則返回0。若因插入而使二叉排序樹 */
/*  失去平衡,則作平衡旋轉處理,布爾變量taller反映T長高與否。 */
Status InsertAVL(BiTree *T,int e,Status *taller)
{  
    if(!*T)
    { 
        /*  插入新結點,樹“長高”,置taller為TRUE */
        *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;
        }
        if (e<(*T)->data)
        { 
            /*  應繼續在T的左子樹中進行搜索 */
            if(!InsertAVL(&(*T)->lchild, e, taller)) /*  未插入 */
                return FALSE;
            if(*taller)                             /*   已插入到T的左子樹中且左子樹“長高” */
                switch((*T)->bf)                 /*  檢查T的平衡度 */
                {
                    case LH:                        /*  原本左子樹比右子樹高,需要作左平衡處理 */
                        LeftBalance(T); 
                        *taller=FALSE; 
                        break;
                    case EH:                        /*  原本左、右子樹等高,現因左子樹增高而使樹增高 */
                        (*T)->bf=LH; 
                        *taller=TRUE; 
                        break;
                    case RH:                        /*  原本右子樹比左子樹高,現左、右子樹等高 */ 
                        (*T)->bf=EH; 
                        *taller=FALSE; 
                        break;
                }
        }
        else
        { 
            /*  應繼續在T的右子樹中進行搜索 */
            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:                        /*  原本右子樹比左子樹高,需要作右平衡處理 */
                        RightBalance(T); 
                        *taller=FALSE; 
                        break;
                }
            }
        }
    }
    return TRUE;
}
 
 
/* 
若在平衡的二叉排序樹t中存在和e有相同關鍵字的結點,則刪除之 
並返回TRUE,否則返回FALSE。若因刪除而使二叉排序樹 
失去平衡,則作平衡旋轉處理,布爾變量shorter反映t變矮與否
*/
int deleteAVL(BiTree *t, int key, int *shorter)
{
    if(*t == NULL)                                      //不存在該元素 
    {
        return FALSE;                                   //刪除失敗 
    }
    else if(key == (*t)->data)                           //找到元素結點
    {
        BitNode *q = NULL;
        if((*t)->lchild == NULL)                     //左子樹為空 
        {
            q = (*t);
            (*t) = (*t)->rchild;
            free(q);
            *shorter = TRUE;
        }
        else if((*t)->rchild == NULL)                    //右子樹為空 
        {
            q = (*t);
            (*t) = (*t)->lchild;
            free(q);
            *shorter = TRUE;
        }
        else                                            //左右子樹都存在,
        {
            q = (*t)->lchild;
            while(q->rchild)
            {
                q = q->rchild;
            }
            (*t)->data = q->data;
            deleteAVL(&(*t)->lchild, q->data, shorter);   //在左子樹中遞歸刪除前驅結點 
        }
    }
    else if(key < (*t)->data)                         //左子樹中繼續查找 
    {
        if(!deleteAVL(&(*t)->lchild, key, shorter))
        {
            return FALSE;
        }
        if(*shorter)
        {
            switch((*t)->bf)
            {
            case LH:
                (*t)->bf = EH;
                *shorter = TRUE;
                break;
            case EH:
                (*t)->bf = RH;
                *shorter = FALSE;
                break;
            case RH:
                RightBalance(&(*t));        //右平衡處理
                if((*t)->rchild->bf == EH)    //注意這里,畫圖思考一下 
                    *shorter = FALSE;
                else
                    *shorter = TRUE;
                break;
            }
        }
    }
    else                                //右子樹中繼續查找 
    {
        if(!deleteAVL(&(*t)->rchild, key, shorter))
        {
            return FALSE;
        }
        if(shorter)
        {
            switch((*t)->bf)
            {
            case LH:
                LeftBalance(&(*t));         //左平衡處理 
                if((*t)->lchild->bf == EH)  //注意這里,畫圖思考一下 
                    *shorter = FALSE;
                else
                    *shorter = TRUE;
                break;
            case EH:
                (*t)->bf = LH;
                *shorter = FALSE;
                break;
            case RH:
                (*t)->bf = EH;
                *shorter = TRUE;
                break;
            }
        }
    }
    return TRUE;
}
 
void InOrderTraverse(BiTree t)
{
    if(t)
    {
        InOrderTraverse(t->lchild);
        printf("%d  ", t->data);
        InOrderTraverse(t->rchild);
    }
}
 
 
int main(void)
{    
    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);
    }
    printf("中序遍歷二叉平衡樹:\n");
    InOrderTraverse(T);
    printf("\n");
    printf("刪除結點元素5后中序遍歷:\n");
    int shorter;
    deleteAVL(&T, 5, &shorter);
    InOrderTraverse(T);
    printf("\n");
    return 0;
}


免責聲明!

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



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