1. 程式人生 > >二叉樹的應用詳解 - 資料結構

二叉樹的應用詳解 - 資料結構

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!

               


概述:

平衡樹——特點:所有結點左右子樹深度差≤1

排序樹——特點:所有結點“左小右大
字典樹——由字串構成的二叉排序樹
判定樹——特點:分支查詢樹(例如12個球如何只稱3次便分出輕重)
帶權樹
——特點:路徑帶權值(例如長度)

最優樹——是帶權路徑長度最短的樹,又稱 Huffman樹,用途之一是通訊中的壓縮編碼。

1. 二叉排序樹(二叉查詢樹 Binary Search Tree):

1.1 二叉排序樹:

或是一棵空樹;或者是具有如下性質的非空二叉樹

 (1)若左子樹不為空,左子樹的所有結點的值均小於根的值;

 (2)若右子樹不為空,右子樹的所有結點均大於根的值;

 (3)它的左右子樹也分別為二叉排序樹。

例:二叉排序樹 如圖9.7:

                 



      二叉排序樹的查詢過程和次優二叉樹類似,通常採取二叉連結串列作為二叉排序樹的儲存結構。中序遍歷二叉排序樹可得到一個關鍵字的有序序列,一個無序序列可以通過構造一棵二叉排序樹變成一個有序序列,構造樹的過程即為對無序序列進行排序的過程。每次插入的新的結點都是二叉排序樹上新的葉子結點,在進行插入操作時,不必移動其它結點,只需改動某個結點的指標,由空變為非空即可。搜尋,插入,刪除的複雜度等於樹高,期望O(logn),最壞O(n)(數列有序,樹退化成線性表).
雖然二叉排序樹的最壞效率是O(n),但它支援動態查詢,且有很多改進版的二叉排序樹可以使樹高為O(logn),如SBT,AVL,紅黑樹等.故不失為一種好的動態排序方法.

2.2 二叉排序樹b中查詢

在二叉排序樹b中查詢x的過程為:

  1. 若b是空樹,則搜尋失敗,否則:
  2. 若x等於b的根節點的資料域之值,則查詢成功;否則:
  3. 若x小於b的根節點的資料域之值,則搜尋左子樹;否則:
  4. 查詢右子樹。

Status SearchBST(BiTree T, KeyType key, BiTree f, BiTree &p){   //在根指標T所指二叉排序樹中遞迴地查詢其關鍵字等於key的資料元素,若查詢成功,   //則指標p指向該資料元素節點,並返回TRUE,否則指標P指向查詢路徑上訪問的   //最好一個節點並返回FALSE,指標f指向T的雙親,其初始呼叫值為NULL   if(!T){ p=f; return FALSE;} //查詢不成功   else if EQ(key, T->data.key) {P=T; return TRUE;} //查詢成功   else if LT(key,T->data.key)     return SearchBST(T->lchild, key, T, p); //在左子樹繼續查詢   else return SearchBST(T->rchild, key, T, p); //在右子樹繼續查詢  

2.3 在二叉排序樹插入結點的演算法

向一個二叉排序樹b中插入一個結點s的演算法,過程為:

  1. 若b是空樹,則將s所指結點作為根結點插入,否則:
  2. 若s->data等於b的根結點的資料域之值,則返回,否則:
  3. 若s->data小於b的根結點的資料域之值,則把s所指結點插入到左子樹中,否則:
  4. 把s所指結點插入到右子樹中。

/*當二叉排序樹T中不存在關鍵字等於e.key的資料元素時,插入e並返回TRUE,否則返回FALSE*/  Status InsertBST(BiTree &T, ElemType e){   if(!SearchBST(T, e.key, NULL,p){    s=(BiTree)malloc(sizeof(BiTNode));    s->data = e; s->lchild = s->rchild = NULL;    if(!p)  T-s;    //被插結點*s為新的根結點    else if LT(e.key, p->data.key) p->lchld = s;  //被子插結點*s為左孩子    else p->rchild = s;  //被插結點*s為右孩子    return TRUE;   }   else return FALSE;  //樹中已有關鍵字相同的結點,不再插入  

2.4 在二叉排序樹刪除結點的演算法

在二叉排序樹刪去一個結點,分三種情況討論:

  1. 若*p結點為葉子結點,即PL(左子樹)和PR(右子樹)均為空樹。由於刪去葉子結點不破壞整棵樹的結構,則只需修改其雙親結點的指標即可。
  2. 若*p結點只有左子樹PL或右子樹PR,此時只要令PL或PR直接成為其雙親結點*f的左子樹即可,作此修改也不破壞二叉排序樹的特性。
  3. 若*p結點的左子樹和右子樹均不空。在刪去*p之後,為保持其它元素之間的相對位置不變,可按中序遍歷保持有序進行調整,可以有兩種做法:其一是令*p的左子樹為*f的左子樹,*s為*f左子樹的最右下的結點,而*p的右子樹為*s的右子樹;其二是令*p的直接前驅(或直接後繼)替代*p,然後再從二叉排序樹中刪去它的直接前驅(或直接後繼)。在二叉排序樹上刪除一個結點的演算法如下:
Status DeleteBST(BiTree &T, KeyType key){   //若二叉排序樹T中存在關鍵字等於key的資料元素時,則刪除該資料元素,並返回   //TRUE;否則返回FALSE    if(!T) return FALSE;    //不存在關鍵字等於key的資料元素   else{    if(EQ(key, T->data.key)) {return Delete(T)};     找到關鍵字等於key的資料元素    else if(LT(key, T->data.key))    return DeleteBST(T->lchild, key);    else return DeleteBST(T->rchild, key);   }  }  Status Delete(BiTree &p){   //從二叉排序樹中刪除結點p,並重接它的左或右子樹    if(!p->rchild){  //右子樹空則只需重接它的左子樹    q=p; p=p->lchild;    free(q);   }   else if(!p->lchild){ //左子樹空只需重接它的右子樹    q=p; p=p->rchild; free(q);   }   else{   //左右子樹均不空    q=p;     s=p->lchild;    while(s->rchild){      q=s;      s=s->rchild    }   //轉左,然後向右到盡頭    p->data = s->data;    //s指向被刪結點的“前驅”    if(q!=p)         q->rchild = s->lchild; //重接*q的右子樹    else      q->lchild = s->lchild;    //重接*q的左子樹    free(s);   }   return TRUE;  }  
[cpp] view plain copy print ?
  1. Status DeleteBST(BiTree &T, KeyType key){  
  2.     //若二叉排序樹T中存在關鍵字等於key的資料元素時,則刪除該資料元素,並返回  
  3.     //TRUE;否則返回FALSE  
  4.     if(!T) return FALSE;    //不存在關鍵字等於key的資料元素  
  5.     else{  
  6.         if(EQ(key, T->data.key)) {return Delete(T)};     找到關鍵字等於key的資料元素  
  7.         else if(LT(key, T->data.key))    return DeleteBST(T->lchild, key);  
  8.         else return DeleteBST(T->rchild, key);  
  9.     }  
  10. }  
  11. Status Delete(BiTree &p){  
  12.     //從二叉排序樹中刪除結點p,並重接它的左或右子樹  
  13.     if(!p->rchild){  //右子樹空則只需重接它的左子樹  
  14.         q=p; p=p->lchild;    free(q);  
  15.     }  
  16.     else if(!p->lchild){ //左子樹空只需重接它的右子樹  
  17.         q=p; p=p->rchild; free(q);  
  18.     }  
  19.     else{   //左右子樹均不空  
  20.         q=p;   
  21.                 s=p->lchild;  
  22.         while(s->rchild){   
  23.                        q=s;   
  24.                        s=s->rchild  
  25.                 }   //轉左,然後向右到盡頭  
  26.         p->data = s->data;    //s指向被刪結點的“前驅”  
  27.         if(q!=p)      
  28.                        q->rchild = s->lchild; //重接*q的右子樹  
  29.         else   
  30.                         q->lchild = s->lchild;    //重接*q的左子樹  
  31.         free(s);  
  32.     }  
  33.     return TRUE;  
  34. }  
Status DeleteBST(BiTree &T, KeyType key){ //若二叉排序樹T中存在關鍵字等於key的資料元素時,則刪除該資料元素,並返回 //TRUE;否則返回FALSE if(!T) return FALSE; //不存在關鍵字等於key的資料元素 else{  if(EQ(key, T->data.key)) {return Delete(T)};  找到關鍵字等於key的資料元素  else if(LT(key, T->data.key)) return DeleteBST(T->lchild, key);  else return DeleteBST(T->rchild, key); }}Status Delete(BiTree &p){ //從二叉排序樹中刪除結點p,並重接它的左或右子樹 if(!p->rchild){ //右子樹空則只需重接它的左子樹  q=p; p=p->lchild; free(q); } else if(!p->lchild){ //左子樹空只需重接它的右子樹  q=p; p=p->rchild; free(q); } else{ //左右子樹均不空  q=p;                 s=p->lchild;  while(s->rchild){                        q=s;                        s=s->rchild                } //轉左,然後向右到盡頭  p->data = s->data; //s指向被刪結點的“前驅”  if(q!=p)                        q->rchild = s->lchild; //重接*q的右子樹  else                         q->lchild = s->lchild; //重接*q的左子樹  free(s); } return TRUE;}

2. 5二叉排序樹效能分析

每個結點的Ci為該結點的層次數。最壞情況下,當先後插入的關鍵字有序時,構成的二叉排序樹蛻變為單支樹,樹的深度為n,其平均查詢長度為 (和順序查詢相同),最好的情況是二叉排序樹的形態和折半查詢的判定樹相同,其平均查詢長度和log2(n)成正比 (O(log2(n)))。



2.6 二叉排序樹的優化
    Size Balanced Tree(SBT)
    AVL樹
    紅黑樹
    Treap(Tree+Heap)
這些均可以使查詢樹的高度為O(logn)

2. 平衡樹二叉樹(又AVL 樹)

1. 1 平衡二叉樹(Balanced Binary Tree)

性質:  左右子樹都是平衡二叉樹且所有結點左、右子樹深度之差的絕對值 ≤ 1。

若定義結點的平衡因子  BF(Balance Factor) = 左子樹深度 –右子樹深度 則:平衡二叉樹中所有結點的BF ∈[ -1, 0, 1 ]

例:判斷下列二叉樹是否AVL樹?




常用演算法有紅黑樹、AVL、Treap、伸展樹等。在平衡二叉搜尋樹中,我們可以看到,其高度一般都良好地維持在O(log2n),大大降低了操作的時間複雜度。

平衡二叉樹是二叉排序樹的另一種形式。

我們希望由任何初始序列構成的二叉排序樹都是平衡二叉樹。因為平衡二叉樹上任何結點的左右子樹的深度之差都不超過1,則可以證明它的深度和logN是同數量級的(其中N是結點的個數)。由此,它的平均查詢長度也和logN同數量級。

C語言描述:

typedef struct  BSTNode {   ElemType    data;   int     bf;     //結點的平衡因子   struct BSTNode  *lchild, *rchild;      //左、右孩子指標  } BSTNode, * BSTree;  

[cpp] view plain copy print ?
  1. typedef struct  BSTNode {  
  2.         ElemType    data;  
  3.         int     bf;     //結點的平衡因子  
  4.         struct BSTNode  *lchild, *rchild;     
  5.                         //左、右孩子指標  
  6.     } BSTNode, * BSTree;  
typedef struct BSTNode {  ElemType data;  int  bf;  //結點的平衡因子  struct BSTNode  *lchild, *rchild;       //左、右孩子指標 } BSTNode, * BSTree;

構造二叉平衡(查詢)樹的方法是:在插入過程中,採用平衡旋轉技術

插入演算法 :

演算法思想:

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

1.若BBST為空樹,則插入一個數據元素為e的新結點作為BBST的根結      點,樹的深度增1; 

2.若e的關鍵字和BBST的根結點的關鍵字相等,則不進行插入; 

3.若e的關鍵字小於BBST的根結點的關鍵字,而且在BBST的左子樹中不存在和e有相同關鍵字的結點,則將e插入在BBST的左子樹上,並且當插入之後的左子樹深度增加(+1)時,分別就下列不同情況處理之:

    iBBST的根結點的平衡因子為-1(右子樹的深度大於左子樹的深度):則將根結點的平衡因子更改為0BBST的深度不變;

    ii.BBST的根結點的平衡因子為0(左、右子樹的深度相等):則將根結點的平衡因子更改為1,BBST的深度增1; 

    iiiBBST的根結點的平衡因子為1(左子樹的深度大於右子樹的深度):

         a. BBST的左子樹根結點的平衡因子為1,則需進行單向右旋平衡處理,並且在右旋處理之後,將根結點和其右子樹根結點的平衡因子更改為0,樹的深度不變;

         b. BBST的左子樹根結點的平衡因子為-1,則需進行先向左、後向右的雙向旋轉平衡處理,並且在旋轉處理之後,修改根結點和其左、右子樹根結點的平衡因子,樹的                深度不變。

4.若e的關鍵字大於BBST的根結點的關鍵字,而且在BBST的右子樹中不存在和e有相同關鍵字的結點,則將e插入在BBST的右子樹上,並且當插入之後的右子樹深度增加(+1)時,分別就不同情況處理之。其處理操作和上述3.中描述相對稱。

typedef struct  BSTNode {   ElemType    data;   int     bf;     //結點的平衡因子   struct BSTNode  *lchild, *rchild;      //左、右孩子指標  } BSTNode, * BSTree;  void R_Rotate (BSTree &p) {   //對以*p為根的二叉排序樹作右旋處理,處理之後p指向新的樹根結點,    //即旋轉處理之前的左子樹的根結點    lc = p->lchild;          //lc指向的*p的左子樹根結點   p->lchild = lc->rchild;       //lc的右子樹掛接為*p的左子樹   lc->rchild = p;     p = lc;             //p指向新的根結點  } // R_Rotate   void L_Rotate (BSTree &p) {   //對以*p為根的二叉排序樹作左旋處理,處理之後p指向新的樹根結點,   //即旋轉處理之前的右子樹的根結點    rc = p->rchild;          //rc指向的*p的右子樹根結點   p->rchild = rc->lchild;       //rc的左子樹掛接為*p的右子樹   rc->lchild = p;     p = rc;             //p指向新的根結點   } // L_Rotate   #define LH  +1      //左高   #define EH  0       //等高   #define RH  -1      //右高   Status InsertAVL (BSTree &T, ElemType e, Boolean &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;    taller = TRUE;   }   else {    if (EQ(e.key, T->data.key))      //樹中已存在和e有相同關鍵字的    {taller = FALSE; return 0;}     //結點則不再插入    if (LT(e.key, T->data.key)) {        //應繼續在*T的左子樹中進行搜尋     if (!InsertAVL(T->lchild, e, taller))    //未插入      return  0;     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;     } // switch (T->bf)    } // if     else {      //應繼續在*T的右子樹中進行搜尋     if (!InsertAVL(T->rchild, e, taller))        //未插入      return  0;     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;     } // switch (T->bf)     } // else    } // else    return  1;   } // InsertAVL   void LeftBalance (BSTree &T) {   //對以指標T所指結點為根的二叉樹作左平衡旋轉處理,本演算法    //結束時,指標T指向新的根結點    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;      break;      } // switch (rd->bf)       rd->bf = EH;      L_Rotate (T->lchild);   //對*T的左子樹作左旋平衡處理      R_Rotate (T);      //對*T作右旋平衡處理   } // switch (lc->bf)   } // LeftBalance  

3. 判定樹(決策樹):

      二分查詢過程可用二叉樹來描述:把當前查詢區間的中間位置上的結點作為根,左子表和右子表中的結點分別作為根的左子樹和右子樹。由此得到的二叉樹,稱為描述二分查詢的判定樹(Decision Tree 決策樹)或比較樹(Comparison Tree)

注意:
     判定樹的形態只與表結點個數n相關,而與輸入例項中R[1..n].keys的取值無關。
【例】具有11個結點的有序表可用下圖所示的判定樹來表示。


舉例:12個球如何用天平只稱3次便分出輕重?

分析:12個球中必有一個非輕即重,即共有24種“次品”的可能性。每次天平稱重的結果有3種,連稱3次應該得到的結果有33=27種。說明僅用3次就能找出次品的可能性是存在的。

思路:首先,將12個球分三組,每組4個,任意取兩組稱。會有兩種情況:平衡,或不平衡。其次,一定要利用已經稱過的那些結論;即充分利用“舊球”的標準性作為參考。



二分查詢判定樹的查詢
二分查詢就是將給定值K與二分查詢判定樹的根結點的關鍵字進行比較。若相等,成功。否則若小於根結點的關鍵字,到左子樹中查詢。若大於根結點的關鍵字,則到右子樹中查詢。
  【例】對於有11個結點的表,若查詢的結點是表中第6個結點,則只需進行一次比較;若查詢的結點是表中第3或第9個結點,則需進行二次比較;找第1,4,7,10個結點需要比較三次;找到第2,5,8,11個結點需要比較四次。
     由此可見,成功的二分查詢過程恰好是走了一條從判定樹的根到被查結點的路徑,經歷比較的關鍵字次數恰為該結點在樹中的層數。若查詢失敗,則其比較過程是經歷了一條從判定樹根到某個外部結點的路徑,所需的關鍵字比較次數是該路徑上內部結點的總數。
    【例】待查表的關鍵字序列為:(05,13,19,21,37,56,64,75,80,88,92),若要查詢K=85的記錄,所經過的內部結點為6、9、10,最後到達方形結點"9-10",其比較次數為3。
     實際上方形結點中"i-i+1"的含意為被查詢值K是介於R[i].key和R[i+1].key之間的,即R[i].key<K<R[i+1].key。
二分查詢的平均查詢長度
      設內部結點的總數為n=2h-1,則判定樹是深度為h=lg(n+1)的滿二叉樹(深度h不計外部結點)。樹中第k層上的結點個數為2k-1,查詢它們所需的比較次數是k。因此在等概率假設下,二分查詢成功時的平均查詢長度為:
           ASLbn≈lg(n+1)-1
  二分查詢在查詢失敗時所需比較的關鍵字個數不超過判定樹的深度,在最壞情況下查詢成功的比較次數也不超過判定樹的深度。即為:
           
  二分查詢的最壞效能和平均效能相當接近。

二分查詢的優點和缺點
  雖然二分查詢的效率高,但是要將表按關鍵字排序。而排序本身是一種很費時的運算。既使採用高效率的排序方法也要花費O(nlgn)的時間。
  二分查詢只適用順序儲存結構。為保持表的有序性,在順序結構裡插入和刪除都必須移動大量的結點。因此,二分查詢特別適用於那種一經建立就很少改動、而又經常需要查詢的線性表。
  對那些查詢少而又經常需要改動的線性表,可採用連結串列作儲存結構,進行順序查詢。連結串列上無法實現二分查詢。

5. 帶權樹:

即路徑帶有權值。例如:



6. 最優樹(赫夫曼樹):

赫夫曼樹:給定n個權值作為n個葉子結點,構造一棵二叉樹,若帶權路徑長度達到最小,稱這樣的二叉樹為最優二叉樹,也稱為赫夫曼樹(Huffman tree)。即帶權路徑長度最短的樹。 赫夫曼樹構造演算法: 假設有n個權值,則構造出的哈夫曼樹有n個葉子結點。 n個權值分別設為{ w1、w2、…、wn},則哈夫曼樹的構造規則為:  (1) 將{w1、w2、…,wn}看成是有n 棵樹的森林(每棵樹僅有一個結點);  (2) 在森林中選出兩個根結點的權值最小的樹合併,作為一棵新樹的左、右子樹,且新樹的根結點權值為其左、右子樹根結點權值之和;  (3)從森林中刪除選取的兩棵樹,並將新樹加入森林;  (4)重複(2)、(3)步,直到森林中只剩一棵樹為止,該樹即為所求得的哈夫曼樹。 赫夫曼編碼:是通訊中最經典的壓縮編碼.
其演算法: stdafx.h檔案:
// stdafx.h : include file for standard system include files,// or project specific include files that are used frequently, but// are changed infrequently//#pragma once#include <stdio.h>  #include "stdlib.h"#include <iostream>using namespace std;//巨集定義    #define TRUE   1    #define FALSE   0    #define OK    1    #define ERROR   0  #define INFEASIBLE -1    #define OVERFLOW -2  #define STACKEMPTY -3 #define QUEUEEMPTY  -3    #define MAX 10 // MAXIMUM STACK CONTENT  #define MAX_QUEUE 10 // MAXIMUM QUEUE CONTENT  typedef int Status;    typedef int ElemType;  typedef struct{ unsigned int weight; unsigned int parent, lchild,rchild;}HTNode, *HuffmanTree;  //動態分配陣列儲存赫夫曼樹typedef char * * HuffmanCode;//動態分配陣列儲存赫夫曼編碼表
test.cpp檔案:
// Test.cpp : Defines the entry point for the console application.  //  #include "stdafx.h"  /************************************************************************//* 演算法:*//************************************************************************/void select(HuffmanTree &HT,int n,int &h1,int &h2)int i ,j; for(i=1;i<=n;i++)  if(!HT[i].parent)  //一旦找到父結點不為0的結點就停止  {   h1=i;        break;  }  for(j=i+1;j<=n;j++)   if(!HT[j].parent)   {    h2=j;    break;   }   for(i=1;i<=n;i++)    if(HT[h1].weight>HT[i].weight&&!HT[i].parent&&(h2!=i))     h1=i;   //進行比較,找權值最小,和h2不同的結點   for(j=1;j<=n;j++)    if(HT[h2].weight>HT[j].weight&&!HT[j].parent&&(h1!=j))     h2=j;   //進行比較,找權值最小,和h1不同的結點   if(h1>h2)   {    int temp;    //將權值最小的結點賦給h1    temp=h1;    h1=h2;    h2=temp;   }}/************************************************************************//*w存放n 個字元的權值(均>0),構造赫夫曼樹HT,並求出n 個字元的赫夫曼編碼HC。*//************************************************************************/void HuffmanCoding(HuffmanTree &HT, HuffmanCode &HC, int *w,int n)if(n<=1) returnint m,i; char *cd; int s1, s2; // HuffmanTree p; m = 2*n-1; HT=(HuffmanTree)malloc((m+1)*sizeof(HTNode));  //0號單元未用 for (i=1; i<=n; i++) { //初始化,相當: p = HT; p = {*w, 0, 0,0 }, ++p;  HT[i].weight=w[i-1];  HT[i].parent=0;  HT[i].lchild=0;  HT[i].rchild=0; } for (i=n+1; i<=m; i++) { //初始化 p = {*w, 0, 0,0 }, ++p;  HT[i].weight=0;  HT[i].parent=0;  HT[i].lchild=0;  HT[i].rchild=0; } //新增檢視便於除錯 printf("\n-------------------------------------------"); printf("\n哈夫曼樹的構造過程如下所示:\n"); printf("HT初態:\n"); printf(" 結點   weight  parent  lchild  rchild"); for (i=1; i<=m; i++)  printf("\n%4d%8d%8d%8d%8d",i,HT[i].weight,HT[i].parent,HT[i].lchild, HT[i].rchild); for (i=n+1; i<=m; i++) { // 建哈夫曼樹  // 在HT[1..i-1]中選擇parent為0且weight最小的兩個結點,  // 其序號分別為s1和s2。  select(HT, i-1,s1,s2);  HT[s1].parent = i; HT[s2].parent = i;  HT[i].lchild = s1; HT[i].rchild = s2;  HT[i].weight = HT[s1].weight + HT[s2].weight;  //新增檢視,便於除錯  printf("\nselect: s1=%d s2=%d\n", s1, s2);  printf(" 結點   weight  parent  lchild  rchild");  for (int j=1; j<=i; j++)   printf("\n%4d%8d%8d%8d%8d",j,HT[j].weight,   HT[j].parent,HT[j].lchild, HT[j].rchild); } //---從葉子到根逆向求每個字元的赫夫曼編碼--- int start,f; unsigned int c; HC=(HuffmanCode)malloc((n+1)*sizeof(char *)); //分配n個字元編碼的頭指標向量 cd=(char *)malloc(n*sizeof(char));     //分配求編碼的工作空間 cd[n-1]='\0';        //編碼結束符 for(i=1;i<=n;++i) {  //逐個字元求赫夫曼編碼  start=n-1;  for(c=i,f=HT[i].parent;f!=0;c=f,f=HT[f].parent)//從葉子到根逆向求編碼   if(HT[f].lchild==c)    cd[--start]='0';   else    cd[--start]='1';  HC[i]=(char *)malloc((n-start)*sizeof(char)); //為第i個字元編碼分配空間  strcpy(HC[i],&cd[start]);  //從cd複製編碼到HC } free(cd);  //釋放工作區間}void main(){ HuffmanTree HT; HuffmanCode HC; int *w,n,i; printf("輸入結點數: "); scanf("%d",&n); HC=(HuffmanCode)malloc(n*sizeof(HuffmanCode)); w=(int *)malloc(n*sizeof(int)); printf("輸入%d個結點的權值: ",n); for(i=0;i<n;i++)  scanf("%d",&w[i]); HuffmanCoding(HT,HC,w,n); printf("\n-------------------------------------------\n"); printf("\n各結點的赫夫曼編碼:\n"); printf("編號  權值  編碼\n"); for(i=1;i<=n;i++)  printf("%2d,%6d:%6s\n",i,w[i-1],HC[i]);}


           

給我老師的人工智慧教程打call!http://blog.csdn.net/jiangjunshow

這裡寫圖片描述