C語言之霍夫曼編碼學習

szjxlyj發表於2015-01-09

  

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五個字母中每個字母的出現頻率,將最小的兩個字母頻率相加合成一個新的節點。如上圖所示,發現FO的頻率最小,故相加2+3=5,將F、O組成一個樹,F為左節點,O為右節點,(FO)為根節點,每個節點的取值為其出現頻率(FO的出現頻率為5)

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

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

5、比較8.10.T,發現8T的頻率最小,故相加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,程式碼實現

  1. #include<stdio.h>
  2. #define n 5 //葉子數目
  3. #define m (2*n-1) //結點總數
  4. #define maxval 10000.0
  5. #define maxsize 100 //哈夫曼編碼的最大位數


  6. //定義結構體
  7. typedef struct FrequencyTreeNode {
  8.     int freq;
  9.     char c;
  10.     struct FrequencyTreeNode *left;
  11.     struct FrequencyTreeNode *right;
  12. } FrequencyTreeNodeStruct, *pFrequencyTreeNodeStruct;


  13. FrequencyTreeNodeStruct frequencies[MAXALPABETNUM];


  14. typedef struct
  15. {
  16.      char bits[n]; //位串
  17.      int start; //編碼在位串中的起始位置
  18.      char ch; //字元
  19. }codetype;


  20. // 讀取檔案內容,統計字元以及出現頻率
  21. void readTxtStatistics(char* fileName)
  22. {
  23.     unsigned int nArray[52] = {0};
  24.     unsigned int i, j;
  25.     char szBuffer[MAXLINE];
  26.     int k=0;
  27.     // 讀取檔案內容
  28.     FILE* fp = fopen(fileName, \"r\");
  29.     if (fp != NULL)
  30.     { /*讀取檔案內容,先統計字母以及出現次數*/
  31.         while(fgets(szBuffer, MAXLINE, fp)!=NULL)
  32.         {
  33.             for(i = 0; i < strlen(szBuffer); i++)
  34.             {
  35.                 if(szBuffer[i] <= \'Z\' && szBuffer[i] >= \'A\')
  36.                 {
  37.                     j = szBuffer[i] - \'A\';
  38.                 }
  39.                 else if(szBuffer[i] <= \'z\' && szBuffer[i] >= \'a\')
  40.                 {
  41.                     j = szBuffer[i] - \'a\' + 26;
  42.                 }
  43.                 else
  44.                 continue;
  45.                 nArray[j]++;
  46.             }
  47.         }


  48.         // 然後賦值給frequencies陣列
  49.         for(i = 0, j = \'A\'; i < 52; i++, j++)
  50.         {
  51.             if (nArray[i] >0)
  52.             {
  53.             /*****/
  54.                 frequencies[k].c=j;
  55.                 frequencies[k].freq=nArray[i];
  56.                 frequencies[k].left=NULL;
  57.                 frequencies[k].right=NULL;
  58.                 k++;
  59.                 printf(\"%c:%d\\n\", j, nArray[i]);
  60.             }
  61.             if(j == \'Z\')
  62.                 j = \'a\' - 1;
  63.         }
  64.     }
  65. }


  66. //建立哈夫曼樹
  67. void huffMan(frequencies tree[]){
  68.     int i,j,p1,p2;//p1,p2分別記住每次合併時權值最小和次小的兩個根結點的下標
  69.     float small1,small2,f;
  70.     char c;
  71.     for(i=0;i<m;i++) //初始化
  72.     {
  73.          tree[i].parent=0;
  74.          tree[i].lchild=-1;
  75.          tree[i].rchild=-1;
  76.          tree[i].weight=0.0;
  77.     }
  78.      printf(\"【依次讀入前%d個結點的字元及權值(中間用空格隔開)】\\n\",n);


  79.     //讀入前n個結點的字元及權值
  80.     for(i=0;i<n;i++)
  81.     {
  82.         printf(\"輸入第%d個字元為和權值\",i+1);
  83.         scanf(\"%c %f\",&c,&f);
  84.         getchar();
  85.         tree[i].ch=c;
  86.         tree[i].weight=f;
  87.     }
  88.     //進行n-1次合併,產生n-1個新結點
  89.     for(i=n;i<m;i++)
  90.     {
  91.          p1=0;p2=0;
  92.          //maxval是float型別的最大值
  93.          small1=maxval;small2=maxval;
  94.          //選出兩個權值最小的根結點
  95.          for(j=0;j<i;j++)
  96.          {
  97.               if(tree[j].parent==0)
  98.               if(tree[j].weight<small1)
  99.                {
  100.                     small2=small1; //改變最小權、次小權及對應的位置
  101.                     small1=tree[j].weight;
  102.                     p2=p1;
  103.                     p1=j;
  104.                }
  105.                else if(tree[j].weight<small2)
  106.                 {
  107.                  small2=tree[j].weight; //改變次小權及位置
  108.                  p2=j;
  109.                 }
  110.                tree[p1].parent=i;
  111.                tree[p2].parent=i;
  112.                tree[i].lchild=p1; //最小權根結點是新結點的左孩子
  113.                tree[i].rchild=p2; //次小權根結點是新結點的右孩子
  114.                tree[i].weight=tree[p1].weight+tree[p2].weight;
  115.         }
  116.    }
  117. }


  118. //根據哈夫曼樹求出哈夫曼編碼,code[]為求出的哈夫曼編碼,tree[]為已知的哈夫曼樹
  119. void huffmancode(codetype code[],frequencies tree[])
  120. {
  121.      int i,c,p;
  122.      codetype cd; //緩衝變數
  123.      for(i=0;i<n;i++)
  124.      {
  125.           cd.start=n;
  126.           cd.ch=tree[i].ch;
  127.           c=i; //從葉結點出發向上回溯
  128.           p=tree[i].parent; //tree[p]是tree[i]的雙親
  129.           while(p!=0)
  130.           {
  131.                cd.start--;
  132.                if(tree[p].lchild==c)
  133.                     cd.bits[cd.start]=\'0\'; //tree[i]是左子樹,生成程式碼\'0\'
  134.                else
  135.                     cd.bits[cd.start]=\'1\'; //tree[i]是右子樹,生成程式碼\'1\'
  136.                c=p;
  137.                p=tree[p].parent;
  138.           }
  139.           code[i]=cd; //第i+1個字元的編碼存入code[i]
  140.      }
  141. }




  142. //根據哈夫曼樹解碼
  143. void decode(hufmtree tree[])
  144. {
  145.      int i,j=0;
  146.      char b[maxsize];
  147.      char endflag=\'2\'; //電文結束標誌取2
  148.      i=m-1; //從根結點開始往下搜尋
  149.      printf(\"輸入傳送的編碼(以\'2\'為結束標誌):\");
  150.      gets(b);
  151.      printf(\"編碼後的字元為\");
  152.      while(b[j]!=\'2\')
  153.      {
  154.           if(b[j]==\'0\')
  155.            i=tree[i].lchild; //走向左子節點
  156.           else
  157.            i=tree[i].rchild; //走向右子節點
  158.           if(tree[i].lchild==-1) //tree[i]是葉結點
  159.           {
  160.              printf(\"%c\",tree[i].ch);
  161.              i=m-1; //回到根結點
  162.           }
  163.           j++;
  164.      }
  165.      printf(\"\\n\");
  166.      if(tree[i].lchild!=-1&&b[j]!=\'2\') //文字讀完,但尚未到葉子結點
  167.      printf(\"\\nERROR\\n\"); //輸入文字有錯
  168. }




  169. void main()
  170. {
  171.      printf(\"---------------—— 哈夫曼編碼實戰 ——\\n\");
  172.      printf(\"總共有%d個字元\\n\",n);
  173.      frequencies tree[m];
  174.      codetype code[n];
  175.      int i,j;//迴圈變數
  176.      huffMan(tree);//建立哈夫曼樹
  177.      huffmancode(code,tree);//根據哈夫曼樹求出哈夫曼編碼
  178.      printf(\"【輸出每個字元的哈夫曼編碼】\\n\");
  179.      for(i=0;i<n;i++)
  180.      {
  181.           printf(\"%c: \",code[i].ch);
  182.           for(j=code[i].start;j<n;j++)
  183.           printf(\"%c \",code[i].bits[j]);
  184.           printf(\"\\n\");
  185.      }
  186.      printf(\"【讀入內容,並進行編碼】\\n\");
  187.      // 開始編碼
  188.      decode(tree);
  189. }

----------------------------------------------------------------------------------------------------------------
有,允許轉載,但必須以連結方式註明源地址,否則追究法律責任!>
原部落格地址: http://blog.itpub.net/26230597/viewspace-1384144/
原作者:黃杉 (mchdba)
----------------------------------------------------------------------------------------------------------------




來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/30078170/viewspace-1396407/,如需轉載,請註明出處,否則將追究法律責任。

相關文章