Huffman编解码
来源:互联网 发布:为知笔记活动 编辑:程序博客网 时间:2024/06/04 19:26
1、 熵,又称为“信息熵” (Entropy)
1) 在信息论中,熵是信息的度量单位。信息论的创始人 Shannon 在其著作《通信的数学理论》中提出了建立在概率统计模型上的信息度量。他把信息定义为“用来消除不确定性的东西”。
2) 一般用符号H 表示,单位是比特。对于任意一个随机变量X,它的熵定义如下:H(X)= - ∑ P(X)log2[P(X)]。
3) 变量的不确定性越大,熵也就越大。换句话说,了解它所需要的信息量也就越大。
1) Huffman Coding (霍夫曼编码)是一种无失真编码的编码方式,Huffman 编码是可变字长编码(VLC)的一种。
2) Huffman 编码基于信源的概率统计模型,它的基本思路是,出现概率大的信源符号编长码,出现概率小的信源符号编短码,从而使平均码长最小。3) 在程序实现中常使用一种叫做树的数据结构实现 Huffman 编码,由它编出的码是即时码。
3 、Huffman 编码的方法
1) 统计符号的发生概率;
2) 把频率按从小到大的顺序排列
3) 每一次选出最小的两个值,作为二叉树的两个叶子节点,将和作为它们的根节点,这两个叶子节点不再参与比较,新的根节点参与比较;
4) 重复 3,直到最后得到和为 1 的根节点;
5) 将形成的二叉树的左节点标 0,右节点标 1,把从最上面的根节点到最下面的叶子节点途中遇到的 0,1 序列串起来,就得到了各个符号的编码。
二、实验过程
1 、 数据结构
1) Huffman 结点
typedef struct huffman_node_tag { unsigned char is Leaf; /* 是否为叶结点*/ unsigned long count; /* 信源中出现频数 */ struct huffman_node_tag *parent; /* 父节点指针 */ union { struct { struct huffman_node_tag *zero, *one; /*如果不是树叶,则此项为该结点左右孩子的指针*/ }; unsigned char symbol; /*如果是树叶,为某个信源符号 */ }; } huffman_node;2)Huffman码字结点
typedef struct huffman_code_tag { /* 码字的长度(单位:位) */ unsigned long numbits; /* 码字, 码字的第 1 位存于 bits[0]的第 1 位, 码字的第 2 位存于 bits[0]的第 2 位, 码字的第 8 位存于 bits[0]的第 8 位, 码字的第 9 位存于 bits[1]的第 1 位 */ unsigned char *bits; } huffman_code;2、Huffman 编码
在实验时采用静态链接库,程序文件中包含两个工程,其中主工程需选择Win32 Console Application, 库工程需选择Win32 Static Library。
1)编码流程
2) 第一次扫描,统计信源字符发生频率(8 比特,共 256 个信源符号)
1> 创建一个 256 个元素的指针数组,用以保存 256 个信源符号的频率。其下标对应相应字符的 ASCII 码。
2> 数组中的非空元素为当前待编码文件中实际出现的信源符号。
3> 程序代码如下:
typedef huffman_node* Symbol Frequencies[MAX_SYMBOLS]; Symbol Frequencies sf; static unsigned int get_symbol_frequencies(Symbol Frequencies *pSF, FILE *in) { int c; /* 总信源符号数初始化为 0 */ unsigned int total_count = 0; /* 将所有信源符号地址初始化为 NULL(0) */ init_frequencies(pSF); /* 第一遍扫描文件*/ while((c = fgetc(in)) != EOF) /*判断文件是否结束*/ { unsigned char uc = c; /* 如果是一个新符号,则产生该字符的一个新叶节点 */ if(!(*pSF)[uc]) (*pSF)[uc] = new_leaf_node(uc); /* 当前字符出现的频数+1 */ ++(*pSF)[uc]->count; /* 总信源符号数+1 */ ++total_count; } return total_count; }3)建立 Huffman 树并计算符号对应的 Huffman 码字
1> 按频率从小到大顺序排序并建立 Huffman 树
tatic Symbol Encoder* calculate_huffman_codes(Symbol Frequencies * pSF) { unsigned int i = 0; unsigned int n = 0; huffman_node *m1 = NULL, *m2 = NULL; Symbol Encoder *pSE = NULL; /*按信源符号出现频率大小排序,小概率符号在前(pSF 数组中)即下标较小 */ qsort((*pSF), MAX_SYMBOLS, sizeof((*pSF)[0]), SFComp); /* 得到当前待编码文件中所出现的信源符号的种类总数 */ for(n = 0; n < MAX_SYMBOLS && (*pSF)[n]; ++n) ; /* 建立 huffman 树。需要合并 n-1 次,所以循环 n-1 次 */ for(i = 0; i < n - 1; ++i) { /*将m1、m2 置为当前频数最小的两个信源符号*/ m1 = (*pSF)[0]; m2 = (*pSF)[1]; /*将m1、m2 合并为一个 huffman 结点加入到数组中,左、右孩子分别置为 m1、m2 的地址,频数为 m1、m2的频数之和*/ (*pSF)[0] = m1->parent = m2->parent = new_nonleaf_node(m1->count + m2->count, m1, m2); (*pSF)[1] = NULL; /*在 m1、m2 合并后重新排序*/ qsort((*pSF), n, sizeof((*pSF)[0]), SFComp); } /* 由建立的 huffman 树对计算每个符号的码字.*/ pSE=(Symbol Encoder*)malloc(sizeof(Symbol Encoder)); memset(pSE, 0, sizeof(Symbol Encoder)); build_symbol_encoder((*p SF)[0], pSE); return pSE; }qsort函数是编译器函数库自带的快速排序函数。函数原型为 void qsort ( void*base , size_t num , size_t width , int (_cdecl*compare)(const void*, const void ));
其中base是排序的一个数组,num是指数组中元素的个数,width是指数组中一个元素的长度,compare是自行设计的比较函数。
compare的函数原型为compare( ( void*)&elem1,( void*)&elem2); 当函数小于0时,elem1排在elem2之前,当函数等于0时,elem1等于elem2,当函数大于0时,elem1排在elem2之后。
2>递归遍历 Huffman树,对存在的每个字符计算码字
typedef huffman_code* Symbol Encoder[MAX_SYMBOLS]; /*256个huffman_code 的指针,位置上对应于 ASCII 的顺序,用于保存码表 */
void build_symbol_encoder(huffman_node *subtree, Symbol Encoder *p SF) { if(subtree == NULL) /*判断是否已到了 root, 是则说明编码过程结束 */ return; if(subtree->is Leaf) /*是叶结点则产生码字*/ (*pSF)[subtree->symbol] = new_code(subtree); else { build_symbol_encoder(subtree->zero, pSF); /* 递归,中序遍历*/ build_symbol_encoder(subtree->one, pSF); } }
huffman_code* new_code(const huffman_node* leaf) { /*创建的Huffman码字是从下向上(即从叶到根)编码的,而实际上的码字是从上向下(即从根到叶)编码的*/ unsigned long numbits = 0; /* 码长 */ unsigned char* bits = NULL; /* 码字首地址*/ huffman_code *p; while(leaf && leaf->parent) /* leaf !=0: 当前字符存在,应该编码;leaf->parent !=0:当前字符的编码仍未完成,即未完成由叶至根的该字符的编码过程*/ { huffman_node *parent = leaf->parent; unsigned char cur_bit = (unsigned char)(numbits % 8); /* 所编位在当前 byte中的位置 */ unsigned long cur_byte = numbits / 8; /* 当前是第几个 byte */ /* realloc它与 malloc 不同,它在保持原有的数据不变的情况下重新分配新的空间,原有数据存在新空间中的前面部分(这里空间的地址可能有变化) */ if(cur_bit == 0) { size_t new Size = cur_byte + 1; bits = (unsigned char*)realloc(bits, new Size); bits[new Size - 1] = 0; /*初始化新分配的 8bit为 0 */ } /* 先遍历左孩子*/ if(leaf == parent->one) bits[cur_byte] |= 1 << cur_bit; /* 左移 1 至当前 byte 的当前位(待编位) */ ++numbits; leaf = parent; /*此时父节点变成了子节点*/ } if(bits) reverse_bits(bits, numbits); /* 整个码字逆序,就得到了实际上的Huffman码字了 */ p = (huffman_code*)malloc(sizeof(huffman_code));p->numbits = numbits; p->bits = bits; /* 整数个字节。与 numbits 配合才可得到真正码字 */ return p; }4)将 Huffman 码表写入文件for(i = 0; i < MAX_SYMBOLS; ++i) { huffman_code *p = (*se)[i]; if(p) { unsigned int numbytes; /* 写符号(1字节)*/ fputc((unsigned char)i, out); /* 写码字的长度(1字节)*/ fputc(p->numbits, out); /* 写码字 */ numbytes = numbytes_from_numbits(p->numbits); if(fwrite(p->bits, 1, numbytes, out) != numbytes) return 1; } }5)第二次扫描文件,对文件查表进行 Huffman 编码,并写入文件int do_file_encode(FILE* in, FILE* out, Symbol Encoder *se) { unsigned char curbyte = 0; unsigned char curbit = 0; int c; while((c = fgetc(in)) != EOF) /* 遍历文件的每一个字符 */ { unsigned char uc = (unsigned char)c; huffman_code *code = (*se)[uc]; /* 查表*/ unsigned long i; for(i = 0; i < code->numbits; ++i) /* 将码字写入文件 */ { /* 将当前位添加到当前字节中 */ curbyte |= get_bit(code->bits, i) << curbit; /*如果当前字节被填满了,那就把它写出来,并重新设置curbyte和curbit*/ if(++curbit == 8) { fputc(curbyte, out); curbyte = 0; curbit = 0; } } } /* 如果在curbyte中有尚未输出的数据,这意味着最后编码的字符没有落在字节边界上,则输出curbyte */ if(curbit > 0) fputc(curbyte, out); return 0; }6)用于统计数据的程序
//step3:add by yzhang for huffman statisticsint huffST_getSymFrequencies(SymbolFrequencies *SF, huffman_stat *st,int total_count){int i,count =0;for(i = 0; i < MAX_SYMBOLS; ++i){if((*SF)[i]){st->freq[i]=(float)(*SF)[i]->count/total_count;count+=(*SF)[i]->count;}else {st->freq[i]= 0;}}if(count==total_count)return 1;elsereturn 0;}int huffST_getcodeword(SymbolEncoder *se, huffman_stat *st){unsigned long i,j;for(i = 0; i < MAX_SYMBOLS; ++i){huffman_code *p = (*se)[i];if(p){unsigned int numbytes; st->numbits[i] = p->numbits;numbytes = numbytes_from_numbits(p->numbits);for (j=0;j<numbytes;j++) st->bits[i][j] = p->bits[j];}elsest->numbits[i] =0;}return 0;}void output_huffman_statistics(huffman_stat *st,FILE *out_Table){int i,j;unsigned char c;fprintf(out_Table,"symbol\t freq\t codelength\t code\n");for(i = 0; i < MAX_SYMBOLS; ++i){fprintf(out_Table,"%d\t ",i);fprintf(out_Table,"%f\t ",st->freq[i]);fprintf(out_Table,"%d\t ",st->numbits[i]);if(st->numbits[i]){for(j = 0; j < st->numbits[i]; ++j){c =get_bit(st->bits[i], j);fprintf(out_Table,"%d",c);}}fprintf(out_Table,"\n");}}//end by yzhang/*huffman_encode_file huffman encodes in to out.*/int huffman_encode_file(FILE *in, FILE *out, FILE *out_Table) //step1:changed by yzhang for huffman statistics from (FILE *in, FILE *out) to (FILE *in, FILE *out, FILE *out_Table){SymbolFrequencies sf;SymbolEncoder *se;huffman_node *root = NULL;int rc;unsigned int symbol_count; //step2:add by yzhang for huffman statisticshuffman_stat hs;//end by yzhang/* Get the frequency of each symbol in the input file. */symbol_count = get_symbol_frequencies(&sf, in); //演示扫描完一遍文件后,SF指针数组的每个元素的构成//step3:add by yzhang for huffman statistics,... get the frequency of each symbol huffST_getSymFrequencies(&sf,&hs,symbol_count); //end by yzhang/* Build an optimal table from the symbolCount. */se = calculate_huffman_codes(&sf);root = sf[0];//step3:add by yzhang for huffman statistics... output the statistics to filehuffST_getcodeword(se, &hs);output_huffman_statistics(&hs,out_Table);//end by yzhang/* Scan the file again and, using the tablepreviously built, encode it into the output file. */rewind(in);rc = write_code_table(out, se, symbol_count);if(rc == 0) rc = do_file_encode(in, out, se);/* Free the Huffman tree. */free_huffman_tree(root);free_encoder(se);return rc;}3 Huffman 解码
1) 解码流程:
2)读取码表并重建据此 Huffman 树huffman_node* read_code_table(FILE* in, unsigned int *pDataBytes) { huffman_node *root = new_nonleaf_node(0, NULL, NULL); unsigned int count; if(fread(&count, sizeof(count), 1, in) != 1) /*得到码表中的符号数*/ { free_huffman_tree(root); return NULL; } /*读取此编码表示的数据字节数 */ if(fread(pDataBytes, sizeof(*pDataBytes), 1, in) != 1) { free_huffman_tree(root); return NULL; } /* 读取条目*/ while(count-- > 0) /*检查是否仍有叶节点未建立,每循环一次建立起一条由根节点至叶结点(符号)的路径*/ { int c; unsigned int curbit; unsigned char symbol; unsigned char numbits; unsigned char numbytes; unsigned char *bytes; huffman_node *p = root; if((c = fgetc(in)) == EOF) { free_huffman_tree(root); return NULL; } symbol = (unsigned char)c; /*符号*/ if((c = fgetc(in)) == EOF) { free_huffman_tree(root); return NULL; } numbits = (unsigned char)c; /*码长*/ numbytes = (unsigned char)numbytes_from_numbits(numbits); bytes = (unsigned char*)malloc(numbytes); /*为读取码字分配空间*/ if(fread(bytes, 1, numbytes, in) != numbytes) /*读取码字*/ { free(bytes); free_huffman_tree(root); return NULL; } /* 将该条目添加到Huffman tree,当前字节的值在父节点的左右孩子之间切换,根据需要在树中添加新节点 */ for(curbit = 0; curbit < numbits; ++curbit) /*读取当前码字的每一位,并依据读取的结果逐步建立起由根节点至该符号叶结点的路径*/ { if(get_bit(bytes, curbit)) /*当前读取位是否为’1’*/ { /*当前读取位为’1’*/ if(p->one == NULL) { p->one = curbit == (unsigned char)(numbits - 1)/*是否是当前码字的最后一位,是,则新建叶结点;不是,则新建非叶结点*/ ? new_leaf_node(symbol) : new_nonleaf_node(0, NULL, NULL); p->one->parent = p; /*‘1’的一枝的父节点指向当前节点*/ } p = p->one; /*沿’1’方向下移一级*/ } else{ /*当前读取位为’0’*/ if(p->zero == NULL) { p->zero = curbit == (unsigned char)(numbits - 1) ? new_leaf_node(symbol) : new_nonleaf_node(0, NULL, NULL); p->zero->parent = p; } p = p->zero; } } free(bytes); } return root; /* 返回 Huffman 树的根结点*/ }3)读取 Huffman 码字,并解码输出Int huffman_decode_file(FILE *in, FILE *out) { huffman_node *root, *p; int c; unsigned int data_count; /*读取Huffman码表*/ root = read_code_table(in, &data_count); if(!root) return 1; /Huffman 树建立失败*/ /*解码文件. */ p = root; while(data_count > 0 && (c = fgetc(in)) != EOF) /*data_count >0 :逻辑上仍有数据;(c = fgetc(in)) != EOF):文件中仍有数据*/ { unsigned char byte = (unsigned char)c; /*1byte的码字*/ unsigned char mask = 1; /* mask用于逐位读出码字*/ while(data_count > 0 && mask) /*loop9: mask = 0x00000000,跳出循环*/ { p = byte & mask ? p->one : p->zero; /*沿 Huffman 树前进*/ mask <<= 1; //loop1: byte & 0x00000001 //loop2: byte & 0x00000010 //…… //loop8: byte & 0x10000000 if(p->is Leaf) /*至叶结点(解码完毕)*/ { fputc(p->symbol, out); p = root; --data_count; } } } free_huffman_tree(root); /*所有 Huffman 码字均已解码输出,文件解码完毕*/ return 0;三、实验结果选择十种不同格式类型的文件,使用Huffman编码器进行压缩得到输出的压缩比特流文件。根据得到的编码结果文件,对各种不同格式的文件进行压缩效率的分析。环境配置及命令行设置如下所示:
(1)下面以表格形式表示的实验结果:
文件类型bmpdocxjpgmp3pdfpngPPTpsdxlsrar平均码长7.3864477.9910197.9205967.9559547.9960657.9304827.6634917.6596814.7712567.999984信源熵(bit/sym)7.3552930977.9850615737.8975167517.93608047.9784562887.906931047.6191211777.630759064.7417977.998687143原文件大小(kB)47633085389476434161860845551495压缩后文件大小(kB)43993085488996535155158263321496压缩比1.08310.9811.0050.9850.9711.0431.0441.6720.999(2)各样本文件的概率分布图,如下所示:
(3)实验结果的分析。根据(1)和(2)的结果,对各种不同类型文件的统计特性和压缩效率进行分析。
根据无失真编码定理可知,对于编码为二进制码的信源符号来说,平均码长的下界为信源熵。当信源符号分布不均匀时,冗余空间越大,通过霍夫曼编码的效率越高;当信源符号均匀分布时,信源的熵最大,冗余空间最小,此时通过霍夫曼编码的效率最低。初出茅庐,还请各位大神多加指教!0 0
- huffman编解码源代码
- 【数据压缩】Huffman编解码
- Huffman编解码
- Huffman编解码
- Huffman编解码
- Huffman编解码
- Huffman编解码完全注释
- Huffman编解码完全注释
- 实验3-huffman编解码
- Huffman编解码实现文本压缩
- Huffman 编解码--这回是正常树~
- C++实现Huffman的编解码
- 实验三—Huffman编解码
- 数据压缩实验三:Huffman编解码
- 基于matlab的huffman编解码
- huffman编解码实现(C语言实现版本)
- huffman编解码算法实验与压缩效率分析
- Huffman 编解码算法实现与压缩效率分析
- 二叉树17:按之字形顺序打印二叉树
- 友元函数和友元类~
- 快排的实现(Java)
- GitHub入门
- 架构之路知多少,开篇只为你知晓
- Huffman编解码
- SPOJ PARADOX Paradox bfs + 缩点
- Problem B: 来开个书店吧
- 二叉树18:二叉树的下一个结点
- 【KNN近邻算法】实现识别简单数字验证码(算法原理+代码笔记)
- JAVA关于Scanner类的分隔符
- Customizing Your Web Browser
- Java知识点(二)——三大特性:封装继承多态
- JavaScript 语法