【筆記】平衡二叉樹

Time-space發表於2017-11-20

  若二叉排序樹的深度為n,在最壞的情況下平均查詢長度為n,為了減少二叉排序樹的查詢次數,需要對二叉排序樹進行平衡化處理,平衡化處理後得到的二叉樹稱為平衡二叉樹。

1.平衡二叉樹的定義

  平衡二叉樹又稱為AVL樹,它或者是一棵空樹,或者左子樹和右子樹都是平衡二叉樹,且左子樹和右子樹的深度之差的絕對值不超過1。
  若將二叉樹中結點的平衡因子BF定義為結點的左子樹的深度和右子樹的深度,則平衡二叉樹中每個結點平衡因子只可能是-1、0、1。如下圖為兩棵平衡二叉樹,結點的右邊數值表示平衡因子,因為該二叉樹既是二叉排序樹又是平衡樹,因此其為平衡二叉樹。


這裡寫圖片描述

  只要二叉樹上有一個結點的平衡因子的絕對值大於1,則該二叉樹就是不平衡的,如下圖所示。


這裡寫圖片描述

  如果二叉排序樹是平衡二叉樹,則其平均查詢長度與log2n

log_2n
是同數量級的。因此在平衡二叉樹上進行查詢的時間複雜度為O(logn)
O(logn)
。若查詢概率不等,為了提高查詢效率,需要對待查記錄序列先進行排序,使其按關鍵字遞增有序,然後構造一棵次優查詢樹。顯然,次優查詢數也是一棵二叉排序樹,但次優查詢樹不能在查詢過程中插入結點生成。一棵二叉排序樹是動態樹表,最優或次優查詢樹是靜態樹表。

2.二叉排序樹的平衡處理

   假設有一個關鍵字序列{5,34,45,76,65},依照此關鍵字序列建立二叉排序樹,且使該二叉排序樹是平衡二叉排序樹,構造過程如下圖所示。


這裡寫圖片描述

   一般情況下,假設由於在二叉排序樹上插入結點而失去平衡的最小子樹根結點的指標為a(即a是裡插入結點最近,且平衡因子絕對值超過1的祖先結點),則失去平衡後進行調整的規律可歸納為下列4種情況:

   1.單向右旋平衡處理:由於在*a的左子樹根結點的左子樹上插入結點,*a的平衡因子由1增至2,致使以*a為根的子樹失去平衡,則需進行一次向右的順時針旋轉操作;
   2.單向左旋平衡處理:由於在*a的右子樹根結點的右子樹插入結點,*a的平衡因子由-1變成-2,致使以*a為根結點的子樹失去平衡,則需進行一次向左的逆時針操作;
   3.雙向旋轉(先左後右)平衡處理:由於在*a的左子樹結點的右子樹上插入結點,*a的平衡因子由1增至2,致使以*a為根結點的子樹失去平衡,則需進行兩次旋轉操作(先左旋後右旋);
   4.雙向旋轉(先右後左)平衡處理:由於在*a的右子樹根結點的左子樹上插入結點,*a的平衡因子由-1變成-2,致使以*a為根結點的子樹失去平衡,則需進行兩次旋轉操作(先右旋後左旋)。


這裡寫圖片描述

   上述4種情況中,1和2對稱,3和4對稱。旋轉操作的正確性容易由“保持二叉排序樹的特性:中序遍歷所得關鍵字序列自小至大有序”證明。無論哪一種情況,在經過平衡旋轉處理之後,以*b或*c為根的新子樹為平衡二叉樹,而且它的深度和插入之前以*a為根的子樹相同。因此當平衡的二叉排序樹因插入結點而失去平衡時,僅需對最小不平衡子樹進行平衡旋轉處理即可。因為經過旋轉處理之後的子樹深度和插入之前相同,因而不影響插入路徑上所有祖先結點的平衡度。

   在平衡二叉排序樹BBST上插入一個新的資料元素e的遞迴演算法可描述如下:

  1. BBST為空樹,則插入一個資料元素為e的新結點作為BBST的根結點,樹的深度增1;
  2. 若e的關鍵字和BBST的根結點的關鍵字相等,則不進行插入;
  3. 若e的關鍵字小於BBST的根結點的關鍵字,而且在BBST的左子樹中不存在和e有相同關鍵字的結點,則將e插入在BBST 的左子樹上,並且當插入之後的左子樹深度增加時,分別就下列不同情況處理:
    (1)BBST的根結點的平衡因子為-1:則將根結點的平衡因子更改為0,BBST的深度不變;
    (2)BBST的根結點的平衡因子為0:則將根結點的平衡因子更改為1,BBST的深度增1;
    (3)BBST的根結點的平衡因子為1:若BBST的左子樹根結點的平衡因子為1,則需進行單向右旋平衡處理,並且在右旋處理之後,將根結點和其右子樹根結點的平衡因子更改為0,樹的深度不變。
    若BBST的左子樹根結點的平衡因子為-1,則需進行先向左、後向右的雙向旋轉平衡處理,並且在旋轉處理之後,修改根結點和其左、右子樹根結點的平衡因子,樹的深度不變;
  4. 若e的關鍵字大於BBST的根結點的關鍵字,而且在BBST的右子樹中不睬做和e有相同關鍵字的即誒但那,則將e插入在BBST的右子樹上,並且當出入之後的右子樹深度增加時,分別就不同情況處理。其處理操作和3中所述相對稱。

3.平衡二叉樹的實現

  • 型別定義
 #define LH +1 /* 左高 */
 #define EH 0  /* 等高 */
 #define RH -1 /* 右高 */
 typedef struct BSTNode
 {
   ElemType data;
   int bf; /* 結點的平衡因子 */
   struct BSTNode *lchild,*rchild; /* 左、右孩子指標 */
 }BSTNode,*BSTree;
  • 初始化查詢表
 Status InitDSTable(BSTree *DT)
 { /* 操作結果: 構造一個空的動態查詢表DT */
   *DT=NULL;
   return OK;
 }
  • 銷燬查詢表
 void DestroyDSTable(BSTree *DT) 
 { /* 初始條件: 動態查詢表DT存在。操作結果: 銷燬動態查詢表DT */
   if(*DT) /* 非空樹 */
   {
     if((*DT)->lchild) /* 有左孩子 */
       DestroyDSTable(&(*DT)->lchild); /* 銷燬左孩子子樹 */
     if((*DT)->rchild) /* 有右孩子 */
       DestroyDSTable(&(*DT)->rchild); /* 銷燬右孩子子樹 */
     free(*DT); /* 釋放根結點 */
     *DT=NULL; /* 空指標賦0 */
   }
 }
  • 查詢操作
 BSTree SearchBST(BSTree T,KeyType key)
 { /* 在根指標T所指二叉排序樹中遞迴地查詢某關鍵字等於key的資料元素, */
   /* 若查詢成功,則返回指向該資料元素結點的指標,否則返回空指標。 */
   if((!T)||EQ(key,T->data.key))
     return T; /* 查詢結束 */
   else if LT(key,T->data.key) /* 在左子樹中繼續查詢 */
     return SearchBST(T->lchild,key);
   else
     return SearchBST(T->rchild,key); /* 在右子樹中繼續查詢 */
 }
  • 遍歷操作
 void TraverseDSTable(BSTree DT,void(*Visit)(ElemType))
 { /* 初始條件: 動態查詢表DT存在,Visit是對結點操作的應用函式 */
   /* 操作結果: 按關鍵字的順序對DT的每個結點呼叫函式Visit()一次且至多一次 */
   if(DT)
   {
     TraverseDSTable(DT->lchild,Visit); /* 先中序遍歷左子樹 */
     Visit(DT->data); /* 再訪問根結點 */
     TraverseDSTable(DT->rchild,Visit); /* 最後中序遍歷右子樹 */
   }
 }
  • 插入操作
Status InsertAVL(BSTree *T,ElemType e,Status *taller)
 { /* 若在平衡的二叉排序樹T中不存在和e有相同關鍵字的結點,則插入一個 */
   /* 資料元素為e的新結點,並返回1,否則返回0。若因插入而使二叉排序樹 */
   /* 失去平衡,則作平衡旋轉處理,布林變數taller反映T長高與否。 */
   if(!*T)
   { /* 插入新結點,樹“長高”,置taller為TRUE */
     *T=(BSTree)malloc(sizeof(BSTNode));
     (*T)->data=e;
     (*T)->lchild=(*T)->rchild=NULL;
     (*T)->bf=EH;
     *taller=TRUE;
   }
   else
   {
     if EQ(e.key,(*T)->data.key)
     { /* 樹中已存在和e有相同關鍵字的結點則不再插入 */
       *taller=FALSE;
       return FALSE;
     }
     if LT(e.key,(*T)->data.key)
     { /* 應繼續在*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;
         }
     }
     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;
     }
     }
   }
   return TRUE;
 }
  • 右旋操作
 void R_Rotate(BSTree *p)
 { /* 對以*p為根的二叉排序樹作右旋處理,處理之後p指向新的樹根結點,即旋轉 */
   /* 處理之前的左子樹的根結點。 */
   BSTree lc;
   lc=(*p)->lchild; /* lc指向p的左子樹根結點 */
   (*p)->lchild=lc->rchild; /* lc的右子樹掛接為p的左子樹 */
   lc->rchild=*p;
   *p=lc; /* p指向新的根結點 */
 }
  • 右平衡旋轉操作
void RightBalance(BSTree *T)
 { /* 對以指標T所指結點為根的二叉樹作右平衡旋轉處理,本演算法結束時, */
   /* 指標T指向新的根結點 */
   BSTree rc,rd;
   rc=(*T)->rchild; /* rc指向*T的右子樹根結點 */
   switch(rc->bf)
   { /* 檢查*T的右子樹的平衡度,並作相應平衡處理 */
     case RH: /* 新結點插入在*T的右孩子的右子樹上,要作單左旋處理 */
              (*T)->bf=rc->bf=EH;
              L_Rotate(T);
              break;
     case LH: /* 新結點插入在*T的右孩子的左子樹上,要作雙旋處理 */
              rd=rc->lchild; /* rd指向*T的右孩子的左子樹根 */
          switch(rd->bf)
              { /* 修改*T及其右孩子的平衡因子 */
                case RH: (*T)->bf=LH;
                         rc->bf=EH;
                         break;
                case EH: (*T)->bf=rc->bf=EH;
                         break;
                case LH: (*T)->bf=EH;
                         rc->bf=RH;
              }
              rd->bf=EH;
              R_Rotate(&(*T)->rchild); /* 對*T的右子樹作右旋平衡處理 */
              L_Rotate(T); /* 對*T作左旋平衡處理 */
   }
 }
  • 左旋操作
 void L_Rotate(BSTree *p)
 { /* 對以*p為根的二叉排序樹作左旋處理,處理之後p指向新的樹根結點,即旋轉 */
   /* 處理之前的右子樹的根結點。 */
   BSTree rc;
   rc=(*p)->rchild; /* rc指向p的右子樹根結點 */
   (*p)->rchild=rc->lchild; /* rc的左子樹掛接為p的右子樹 */
   rc->lchild=*p;
   *p=rc; /* p指向新的根結點 */
 }
  • 左平衡旋轉操作
void LeftBalance(BSTree *T)
 { /* 對以指標T所指結點為根的二叉樹作左平衡旋轉處理,本演算法結束時, */
   /* 指標T指向新的根結點。 */
   BSTree lc,rd;
   lc=(*T)->lchild; /* lc指向*T的左子樹根結點 */
   switch(lc->bf)
   { /* 檢查*T的左子樹的平衡度,並作相應平衡處理 */
     case LH: /* 新結點插入在*T的左孩子的左子樹上,要作單右旋處理 */
              (*T)->bf=lc->bf=EH;
              R_Rotate(T);
              break;
     case RH: /* 新結點插入在*T的左孩子的右子樹上,要作雙旋處理 */
              rd=lc->rchild; /* rd指向*T的左孩子的右子樹根 */
              switch(rd->bf)
              { /* 修改*T及其左孩子的平衡因子 */
                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;
          }
              rd->bf=EH;
              L_Rotate(&(*T)->lchild); /* 對*T的左子樹作左旋平衡處理 */
              R_Rotate(T); /* 對*T作右旋平衡處理 */
   }
 }

相關文章