歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> C語言之霍夫曼編碼學習

C語言之霍夫曼編碼學習

日期:2017/3/1 9:35:50   编辑:Linux編程

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