歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

C語言之霍夫曼編碼學習

1,霍夫曼編碼描述

哈夫曼樹─即最優二叉樹,帶權路徑長度最小的二叉樹,經常應用於數據壓縮。 在計算機信息處理中,“哈夫曼編碼”是一種一致性編碼法(又稱“熵編碼法”),用於數據的無損耗壓縮。這一術語是指使用一張特殊的編碼表將源字符(例如某文件中的一個符號)進行編碼。這張編碼表的特殊之處在於,它是根據每一個源字符出現的估算概率而建立起來的(出現概率高的字符使用較短的編碼,反之出現概率低的則使用較長的編碼,這便使編碼之後的字符串的平均期望長度降低,從而達到無損壓縮數據的目的)。這種方法是由David.A.Huffman發展起來的。 例如,在英文中,e的出現概率很高,而z的出現概率則最低。當利用哈夫曼編碼對一篇英文進行壓縮時,e極有可能用一個位(bit)來表示,而z則可能花去25個位(不是26)。用普通的表示方法時,每個英文字母均占用一個字節(byte),即8個位。二者相比,e使用了一般編碼的1/8的長度,z則使用了3倍多。若能實現對於英文中各個字母出現概率的較准確的估算,就可以大幅度提高無損壓縮的比例。

2,問題描述
霍夫曼編碼前首先要統計每個字的字頻,即出現次數,例如:

 

1、將所有字母出現的次數以從小到大的順序排序,如上圖

2、每個字母都代表一個終端節點(葉節點),比較F.O.R.G.E.T五個字母中每個字母的出現頻率,將最小的兩個字母頻率相加合成一個新的節點。如上圖所示,發現F與O的頻率最小,故相加2+3=5,將F、O組成一個樹,F為左節點,O為右節點,(FO)為根節點,每個節點的取值為其出現頻率(FO的出現頻率為5)

3、比較5.R.G.E.T,發現R與G的頻率最小,故相加4+4=8,將RG組成一個新的節點

4、比較5.8.E.T,發現5與E的頻率最小,故相加5+5=10,因此將FO作為左節點,E作為右節點,FOE作為根節點

5、比較8.10.T,發現8與T的頻率最小,故相加8+7=15,將RG作為左節點,T作為右節點,RGT作為根節點

6、最後剩10.15,沒有可以比較的對象,相加10+15=25,FOE作為左節點,RGT作為右節點

根節點不取值,每個左子節點取值0,右子節點取值1,將每個字母從根節點開始遍歷,沿途的取值組成編碼:

 

首先選擇一個文本,統計每個字符出現的次數,組成以下數組:
typedef struct FrequencyTreeNode {
    int freq;
    char c;
    struct FrequencyTreeNode *left;
    struct FrequencyTreeNode *right;
} FrequencyTreeNodeStruct, *pFrequencyTreeNodeStruct;

然後將獲得的數組frequencies進行排序,按照freq由小到大的順序組成一個二叉查找樹,FrequencyTreeNodeStruct,從二叉查找樹中找到最小的節點,從樹中刪除,再取最小的節點,兩個子節點,組成一個新的樹,根節點c為0,freq為兩個子節點的和,加入frequencies中,並排序,重復該步驟,一直到frequencies中只有一個節點,則該節點為Huffman coding tree的根節點

以short類型按照前述的規則為每個字符編碼,爾後將文本翻譯為Huffman coding,再通過Huffman coding tree進行解碼,驗證編碼的正確性。

3,代碼實現

#include<stdio.h>

#define n 5 //葉子數目

#define m (2*n-1) //結點總數

#define maxval 10000.0

#define maxsize 100 //哈夫曼編碼的最大位數

 

 

//定義結構體

typedef struct FrequencyTreeNode {

    int freq;

    char c;

    struct FrequencyTreeNode *left;

    struct FrequencyTreeNode *right;

} FrequencyTreeNodeStruct, *pFrequencyTreeNodeStruct;

 

 

FrequencyTreeNodeStruct frequencies[MAXALPABETNUM];

 

 

typedef struct

{

    char bits[n]; //位串

    int start; //編碼在位串中的起始位置

    char ch; //字符

}codetype;

 

 

// 讀取文件內容,統計字符以及出現頻率

void readTxtStatistics(char* fileName)

{

    unsigned int nArray[52] = {0};

    unsigned int i, j;

    char szBuffer[MAXLINE];

    int k=0;

    // 讀取文件內容

    FILE* fp = fopen(fileName, \"r\");

    if (fp != NULL)

    { /*讀取文件內容,先統計字母以及出現次數*/

        while(fgets(szBuffer, MAXLINE, fp)!=NULL)

        {

            for(i = 0; i < strlen(szBuffer); i++)

            {

                if(szBuffer[i] <= \'Z\' && szBuffer[i] >= \'A\')

                {

                    j = szBuffer[i] - \'A\';

                }

                else if(szBuffer[i] <= \'z\' && szBuffer[i] >= \'a\')

                {

                    j = szBuffer[i] - \'a\' + 26;

                }

                else

                continue;

                nArray[j]++;

            }

        }

 

 

        // 然後賦值給frequencies數組

        for(i = 0, j = \'A\'; i < 52; i++, j++)

        {

            if (nArray[i] >0)

            {

            /*****/

                frequencies[k].c=j;

                frequencies[k].freq=nArray[i];

                frequencies[k].left=NULL;

                frequencies[k].right=NULL;

                k++;

                printf(\"%c:%d\\n\", j, nArray[i]);

            }

            if(j == \'Z\')

                j = \'a\' - 1;

        }

    }

}

 

 

//建立哈夫曼樹

void huffMan(frequencies tree[]){

    int i,j,p1,p2;//p1,p2分別記住每次合並時權值最小和次小的兩個根結點的下標

    float small1,small2,f;

    char c;

    for(i=0;i<m;i++) //初始化

    {

        tree[i].parent=0;

        tree[i].lchild=-1;

        tree[i].rchild=-1;

        tree[i].weight=0.0;

    }

    printf(\"【依次讀入前%d個結點的字符及權值(中間用空格隔開)】\\n\",n);

 

 

    //讀入前n個結點的字符及權值

    for(i=0;i<n;i++)

    {

        printf(\"輸入第%d個字符為和權值\",i+1);

        scanf(\"%c %f\",&c,&f);

        getchar();

        tree[i].ch=c;

        tree[i].weight=f;

    }

    //進行n-1次合並,產生n-1個新結點

    for(i=n;i<m;i++)

    {

        p1=0;p2=0;

        //maxval是float類型的最大值

        small1=maxval;small2=maxval;

        //選出兩個權值最小的根結點

        for(j=0;j<i;j++)

        {

              if(tree[j].parent==0)

              if(tree[j].weight<small1)

              {

                    small2=small1; //改變最小權、次小權及對應的位置

                    small1=tree[j].weight;

                    p2=p1;

                    p1=j;

              }

              else if(tree[j].weight<small2)

                {

                small2=tree[j].weight; //改變次小權及位置

                p2=j;

                }

              tree[p1].parent=i;

              tree[p2].parent=i;

              tree[i].lchild=p1; //最小權根結點是新結點的左孩子

              tree[i].rchild=p2; //次小權根結點是新結點的右孩子

              tree[i].weight=tree[p1].weight+tree[p2].weight;

        }

  }

}

 

 

//根據哈夫曼樹求出哈夫曼編碼,code[]為求出的哈夫曼編碼,tree[]為已知的哈夫曼樹

void huffmancode(codetype code[],frequencies tree[])

{

    int i,c,p;

    codetype cd; //緩沖變量

    for(i=0;i<n;i++)

    {

          cd.start=n;

          cd.ch=tree[i].ch;

          c=i; //從葉結點出發向上回溯

          p=tree[i].parent; //tree[p]是tree[i]的雙親

          while(p!=0)

          {

              cd.start--;

              if(tree[p].lchild==c)

                    cd.bits[cd.start]=\'0\'; //tree[i]是左子樹,生成代碼\'0\'

              else

                    cd.bits[cd.start]=\'1\'; //tree[i]是右子樹,生成代碼\'1\'

              c=p;

              p=tree[p].parent;

          }

          code[i]=cd; //第i+1個字符的編碼存入code[i]

    }

}

 

 

 

 

//根據哈夫曼樹解碼

void decode(hufmtree tree[])

{

    int i,j=0;

    char b[maxsize];

    char endflag=\'2\'; //電文結束標志取2

    i=m-1; //從根結點開始往下搜索

    printf(\"輸入發送的編碼(以\'2\'為結束標志):\");

    gets(b);

    printf(\"編碼後的字符為\");

    while(b[j]!=\'2\')

    {

          if(b[j]==\'0\')

          i=tree[i].lchild; //走向左子節點

          else

          i=tree[i].rchild; //走向右子節點

          if(tree[i].lchild==-1) //tree[i]是葉結點

          {

            printf(\"%c\",tree[i].ch);

            i=m-1; //回到根結點

          }

          j++;

    }

    printf(\"\\n\");

    if(tree[i].lchild!=-1&&b[j]!=\'2\') //文本讀完,但尚未到葉子結點

    printf(\"\\nERROR\\n\"); //輸入文本有錯

}

 

void main()

{

    printf(\"---------------—— 哈夫曼編碼實戰 ——\\n\");

    printf(\"總共有%d個字符\\n\",n);

    frequencies tree[m];

    codetype code[n];

    int i,j;//循環變量

    huffMan(tree);//建立哈夫曼樹

    huffmancode(code,tree);//根據哈夫曼樹求出哈夫曼編碼

    printf(\"【輸出每個字符的哈夫曼編碼】\\n\");

    for(i=0;i<n;i++)

    {

          printf(\"%c: \",code[i].ch);

          for(j=code[i].start;j<n;j++)

          printf(\"%c \",code[i].bits[j]);

          printf(\"\\n\");

    }

    printf(\"【讀入內容,並進行編碼】\\n\");

    // 開始編碼

    decode(tree);

}

C++ Primer Plus 第6版 中文版 清晰有書簽PDF+源代碼 http://www.linuxidc.com/Linux/2014-05/101227.htm

讀C++ Primer 之構造函數陷阱 http://www.linuxidc.com/Linux/2011-08/40176.htm

讀C++ Primer 之智能指針 http://www.linuxidc.com/Linux/2011-08/40177.htm

讀C++ Primer 之句柄類 http://www.linuxidc.com/Linux/2011-08/40175.htm

將C語言梳理一下,分布在以下10個章節中:

  1. Linux-C成長之路(一):Linux下C編程概要 http://www.linuxidc.com/Linux/2014-05/101242.htm
  2. Linux-C成長之路(二):基本數據類型 http://www.linuxidc.com/Linux/2014-05/101242p2.htm
  3. Linux-C成長之路(三):基本IO函數操作 http://www.linuxidc.com/Linux/2014-05/101242p3.htm
  4. Linux-C成長之路(四):運算符 http://www.linuxidc.com/Linux/2014-05/101242p4.htm
  5. Linux-C成長之路(五):控制流 http://www.linuxidc.com/Linux/2014-05/101242p5.htm
  6. Linux-C成長之路(六):函數要義 http://www.linuxidc.com/Linux/2014-05/101242p6.htm
  7. Linux-C成長之路(七):數組與指針 http://www.linuxidc.com/Linux/2014-05/101242p7.htm
  8. Linux-C成長之路(八):存儲類,動態內存 http://www.linuxidc.com/Linux/2014-05/101242p8.htm
  9. Linux-C成長之路(九):復合數據類型 http://www.linuxidc.com/Linux/2014-05/101242p9.htm
  10. Linux-C成長之路(十):其他高級議題

Copyright © Linux教程網 All Rights Reserved