1. 程式人生 > >Huffman(哈夫曼)樹編碼與解碼程式(全)

Huffman(哈夫曼)樹編碼與解碼程式(全)

關於Huffman樹構建與編碼的原理,很多書上有介紹,我在這裡就只給出相應的程式,包括樹的構建,2種編碼方法,譯碼(這部分是我自己獨立寫的,肯定有不當之處,歡迎回帖指正)等,裡面註釋也很清晰,費了很大勁,希望對大家有幫助。

<span style="font-size:18px;"><span style="font-size:16px;">//[email protected]
#include <iostream>
using namespace std;
 
int m, s1, s2;      // m是總結點個數,s1,s2用於篩選出最小和第二小的兩個數

typedef struct{
    unsigned int weight;
    unsigned int parent, lchild, rchild;
}HTNode, *HuffmanTree;      //動態分配陣列儲存哈夫曼樹

typedef char* HuffmanCode;   //動態分配陣列儲存哈夫曼編碼表

//選出weight最小的兩個結點,s1儲存最小的,s2儲存第二小的
void SelectMin(HuffmanTree HT, int nNode)
{
    int i, j;
    for(i = 1; i <= nNode; i++)
        if(!HT[i].parent)
        {
            s1 = i;
            break;
        }
    for(j = i+1; j <= nNode; j++)
        if(!HT[j].parent)
        {
            s2 = j;
            break;
        }

    for(i = 1; i <= nNode; i++)
        if((HT[i].weight < HT[s1].weight) && (!HT[i].parent) && (s2 != i))
            s1 = i;
    for(j = 1; j <= nNode; j++)
        if((HT[j].weight < HT[s2].weight) && (!HT[j].parent) && (s1 != j))
            s2 = j;
    // 以上只篩選出最小的兩個,這裡保證s1的weight比s2的小
    if(HT[s1].weight > HT[s2].weight)
    {
        int tmp = s1;
        s1 = s2;
        s2 = tmp;
    }
}

// w[]存放nNode個字元的權值(均大於0),構造哈夫曼樹HT,
// 並求出nNode個字元的哈夫曼編碼HC
void HuffmanCoding(HuffmanTree &HT, HuffmanCode *&HC, int *w, int nNode)
{
    int i, j;
    char *hfcode;
    int p;
    int cdlen;
    if(nNode < 1)
        return;
    m = 2*nNode-1;   //哈夫曼樹的結點數,定理公式
 
    /////////////////////////////以下是求Huffman樹的初始化/////////////////////////////
    HT = (HTNode*) malloc ((m+1) *sizeof(HTNode));  //0號單元未用
    for(i = 1; i <= nNode; i++)    //初始化
    {
        HT[i].weight = w[i-1];
        HT[i].parent = 0;
        HT[i].lchild = 0;
        HT[i].rchild = 0;
    }
    for(i = nNode+1; i <= m; i++)
    {
        HT[i].weight = 0;
        HT[i].parent = 0;
        HT[i].lchild = 0;
        HT[i].rchild = 0;
    }

    /////////////////////////////以下是Huffman樹的構建/////////////////////////////
    for(i = nNode+1; i <= m; i++)
    {
        // 建立哈夫曼樹
        // 在HT[1..i-1]中選擇parent為0且weight最小的兩個節點
        // 其序號分別是s1和s2,並且小的是左孩子 
        SelectMin(HT, i-1);
        HT[s1].parent = i;
        HT[s2].parent = i;
        //cout << "S1 && S2: " << HT[s1].weight << " " << HT[s2].weight << endl;
        HT[i].lchild = s1;
        HT[i].rchild = s2;
        HT[i].weight = HT[s1].weight + HT[s2].weight;
    }
 
 
    /////////////////////////////以下是求Huffman樹的編碼/////////////////////////////
    
    ////////////////////////方法一/////////////////////////// 
    /*該方法是從每個葉結點開始上溯,以從後向前的方式生成huffman編碼*/ 
    hfcode = (char *) malloc ( (nNode + 1) * sizeof( char ) );
    hfcode[nNode] = '\0';   //編碼以‘\0’結尾
    int start;
    int c;   //c:當前處理節點,p是c的父結點
    for(int i=1; i<=nNode; i++) 
    {
        start = nNode;
        for(c=i, p=HT[c].parent; p!=0; c=p,p=HT[p].parent)
        {
            if(c==HT[p].lchild)
                hfcode[--start]='0';
            else if(c==HT[p].rchild)
                hfcode[--start]='1';
        }
        //申請足夠存放該節點編碼就行,不浪費;
        HC[i] = (char *) malloc ((nNode-start+1) * sizeof(char));  
        strcpy(HC[i], &hfcode[start]);
    }
    free(hfcode); 
     
    ////////////////////////方法二///////////////////////////
    // 該方法從根出發,遞迴遍歷哈夫曼樹,求得編碼。標記0,1,2含義如下: 
    //0:搜到一個滿足條件的新結點
    //1:當前正在搜其兒子結點的結點,還沒有回溯回來 
    //2:不滿足條件或者兒子結點已全部搜完,已經回溯回來,則回溯到其父結點
    //注意:當流程走到一個結點後,其標記立即變為下一狀態,
    //即:0->1->2->0(最後一步2->0不是必需的),但執行條件仍然是當前狀態 
    
    /*hfcode = (char *) malloc (nNode * sizeof(char));   //分配求編碼的工作空間
    p = m;
    cdlen = 0;
    for(i = 1; i <= m; i++)
        HT[i].weight = 0;   //遍歷哈夫曼樹時用作結點狀態的標誌
    
    while(p)        //退出條件:p = 結點m的parent,即為0
    {
        if(HT[p].weight == 0)       //向左走 
        {
            HT[p].weight = 1;
            if(HT[p].lchild != 0)
            {
                p = HT[p].lchild;
                hfcode[cdlen++] = '0';
            }
            //else if(HT[p].rchild == 0)  //左右孩子都為0,葉結點 
            //{
            //  HC[p] = (char *) malloc ((cdlen+1) * sizeof(char));
            //  hfcode[cdlen] = '\0';   //保證後面的不會被複制
            //  strcpy(HC[p], hfcode);   //複製編碼
            //}
        }
        else if(HT[p].weight == 1)   //向右走 
        {
            HT[p].weight = 2;
            if(HT[p].rchild != 0)
            {
                p = HT[p].rchild;
                hfcode[cdlen++] = '1';
            }
            //該分支放在這裡似乎更合理一點,放上面被註釋掉的地方也可以 
            else if(HT[p].rchild == 0)  //左右孩子都為0,葉結點 
            {
                HC[p] = (char *) malloc ((cdlen+1) * sizeof(char));
                hfcode[cdlen] = '\0';   //保證後面的不會被複制
                strcpy(HC[p], hfcode);   //複製編碼
            }
        }
        else    //HT[p].weight == 2 退回到父結點,編碼長度減一
        {
            HT[p].weight = 0;
            p = HT[p].parent;
            --cdlen;
        }
    }*/
}
/*Huffman解碼函式
 *HT:Huffman樹,w[]:權值陣列(從下標0開始),code[]:要解碼的串
 */ 
void HuffmanDecode(HuffmanTree HT, int w[], char code[])
{
    char *ch = code;
    int i;
    while( *ch != '\0' ){
        //解碼一個結點每次都從樹根m開始
        for(i=m; HT[i].lchild !=0 && HT[i].rchild != 0; ){
            if( *ch == '0' )
                i = HT[i].lchild;
            else if( *ch == '1' )
                i = HT[i].rchild;
            ++ch;
        }
        cout<<w[i-1]<<" ";
    }
}

int main()
{
    HuffmanTree HT = NULL;   // 哈夫曼樹
    HuffmanCode *HC;    // 儲存哈夫曼編碼
    int *w, nNode, i;   // w記錄權值
    char CodeStr[20]= {0};  //存放編碼後的串
    cout<<"輸入結點數(>=2): "<<endl;
    cin>>nNode;
    HC = (HuffmanCode *) malloc (nNode* sizeof(HuffmanCode));
    w = (int *) malloc (nNode * sizeof(int));
    cout<<"輸入 "<<nNode<<" 個結點的權值\n";
    for(i = 0; i < nNode; i++)
        scanf("%d", &w[i]);
    HuffmanCoding(HT, HC, w, nNode);
    cout<<"\n各結點的哈夫曼編碼:"<<endl;
    for(i = 1; i <= nNode; i++){ 
        printf("%2d(%d):%s\n", i, w[i-1], HC[i]);
        strcat(CodeStr, HC[nNode-i+1]); //簡單生成一個huffman碼串
    }

    cout<<"對哈夫曼編碼\""<<CodeStr<<"\"的解碼如下:"<<endl;
    HuffmanDecode(HT, w, CodeStr);

    return 0;
}</span></span>