哈夫曼樹

  哈夫曼樹又稱最優二叉樹,是一種帶權路徑長度最短的二叉樹。所謂樹的帶權路徑長度,就是樹中全部的葉結點的權值乘上其到根結點的路徑長度(若根結點爲0層,葉結點到根結點的路徑長度爲葉結點的層數)。樹的帶權路徑長度記爲WPL=(W1*L1+W2*L2+W3*L3+...+ Wn*Ln)N個權值Wi(i=1,2,...n)構成一棵有N個葉結點的二叉樹,相應的葉結點的路徑長度爲Li(i=1,2,...n)。能夠證實哈夫曼樹的WPL是最小的。
        構造哈夫曼樹的算法以下:
        1
)對給定的n個權值{W1,W2,W3,...,Wi,...,Wn}構成n棵二叉樹的初始集合F={T1,T2,T3,...,Ti,..., Tn},其中每棵二叉樹Ti中只有一個權值爲Wi的根結點,它的左右子樹均爲空。
        2
)在F中選取兩棵根結點權值最小的樹做爲新構造的二叉樹的左右子樹,新二叉樹的根結點的權值爲其左右子樹的根結點的權值之和。
        3
)從F中刪除這兩棵樹,並把這棵新的二叉樹一樣以升序排列加入到集合F中。
        4
)重複2)和3),直到集合F中只有一棵二叉樹爲止。java

        例如,對於4個權值爲1357的節點構造一棵哈夫曼樹,其構造過程以下圖所示:算法

 

huf.jpeg

 能夠計算獲得該哈夫曼樹的路徑長度WPL(1+3)*3+2*5+1*7=26。數組

        對於哈夫曼樹,有一個很重要的定理:對於具備n個葉子節點的哈夫曼樹,共有2*n-1個節點。post

        這個定理的解釋以下:對於二叉樹來講,有三種類型節點,即度數(只算出度)爲2的節點,度數爲1的節點和度數爲0的葉節點。而哈夫曼樹的非葉子節點是由兩個節點生成的,所以不能出現度數爲1的節點,而生成的非葉子節點的個數爲葉子節點個數減一,於此定理就得證了。編碼

        這裏給出構造哈夫曼樹的算法(算法實現使用C語言而不是java)。出於簡單性考慮,構造的哈夫曼樹不是採用鏈式存儲,而是以數組方式存儲,其中使用數組位置索引標識節點的連接。對於哈夫曼樹中的節點其數據類型以下:spa

?
typedef struct QHTNode{
     char c;      //存儲的數據,爲一個字符
     double weight; //節點權重
     int parent; //父節點在數組中的位置索引
     int lchild; //左孩子在數組中的位置索引
     int rchild; //右孩子在數組中的位置索引
}HTNode;

    構造哈夫曼樹的算法的實現原理以下:對於n個葉子節點,咱們根據上面的定理構造出大小爲2*n-1的數組來存放整個哈夫曼樹。這個數組的前n個位置存放的爲已知的葉子節點,後(n-1)個位置存放的爲動態生成的樹內節點。在算法的大循環過程當中,要作的事情就是根據位置i前面的已知節點(或者是葉節點或者是生成的樹內節點),找出parent爲-1(即節點尚且是一個子樹的根結點)的節點中權值最小的兩個節點,而後根據這兩個節點構造出位置爲i的新的父節點(也就是一棵新樹的根結點)。程序以下:code

?
void creatHuffmanTree(HTNode ht[], int n){
     int i,j;
     int lchild,rchild;
     double minL,minR;
     for (i=0;i<2*n-1;i++){
         ht[i].parent = ht[i].lchild = ht[i].rchild = -1;
     }
     for (i=n;i<2*n-1;i++){
         minL = minR = MAXNUMBER;
         lchild = rchild = -1;
         for (j=0;j<i;j++){
             if (ht[j].parent == -1){
                 if (ht[j].weight < minL){
                     minR = minL;
                     minL = ht[j].weight;
                     rchild = lchild;
                     lchild = j;
                 } else if (ht[j].weight < minR){
                     minR = ht[j].weight;
                     rchild = j;
                 }
             }
         }
         ht[lchild].parent = ht[rchild].parent = i;
         ht[i].weight = minL + minR;
         ht[i].lchild = lchild;
         ht[i].rchild = rchild;
     }   
}

   哈夫曼樹的一個經典應用就是哈夫曼編碼。在數據通訊中,常常須要將傳送的文字轉換成二進制字符串,這個過程就是編碼。哈夫曼編碼是一種變長的編碼方案,其核心就是使頻率越高的碼元(這個詞不知用的是否準確,就是要編碼的對象,能夠是字符串等等了)採用越短的編碼。編碼過程就根據不一樣碼元的頻率(至關於權值)構造出哈夫曼樹,而後求葉子節點到根節點的路徑,其中節點的左孩子路徑標識爲0,右孩子路徑標識爲1。對於上面的例子,權值爲1的節點編碼爲000,權值爲3的節點編碼爲001,權值爲5的節點編碼爲01,權值爲7的節點編碼爲1對象

        下面的實現採用的方法是從葉子節點向上遍歷到根結點,其中數據類型 HCode中的 code存儲路徑信息,而start表示路徑信息是從code數組的start位置開始的,結束位置爲節點數nblog

?
typedef struct QHCode{
     char * code;
     int start;
}Hcode;
 
void createHuffmanCode(HTNode ht[],HCode hc[], int n){
     int i,f,c;
     HCode father;
     for (i=0;i<n;i++){
         hc[i].start = n;
         c = i;
         while ((f=ht[c].parent) != -1){
             if (ht[f].lchild == c){
                 hc[i].code[hc[i].start--] = '0' ;
             } else {
                 hc[i].code[hc[i].start--] = '1' ;
             }
             c = f;
         }
         hc[i].start++;
     }
}
相關文章
相關標籤/搜索