test3_Huffman codes

Huffman编码的方法

  (1)统计符号发生的概率。 

       (2)按照出现概率从小到大排序。

       (3)每一次选出概率最小的两个符号作为二叉树的叶节点,将和作为它们的根节点,其频率为两个子节点频率之和,这两个叶子节点不再参与比较,再用新的根节点参与比较。
  (4)重复(3)步骤,直到得到概率为1的根节点。

       (5)二叉树的左节点为0,右节点为1,从上到下由根节点到叶节点得到每个叶节点的编码。

Huffman节点及Huffman码字节点数据结构

[cpp]  view plain  copy
  1. <strong> typedef struct huffman_node_tag   
  2. {  
  3.     unsigned char isLeaf; // 是否为叶节点,1是0否  
  4.     unsigned long count; //信源中出现频数  
  5.     struct huffman_node_tag *parent; //父节点指针  
  6.     union{  
  7.         struct//如果不是叶节点,这里为左右子节点指针  
  8.             struct huffman_node_tag *zero, *one;  
  9.         };  
  10.         unsigned char symbol; //如果是叶节点,这里为一个信源符号  
  11.     };  
  12. } huffman_node;  
  13.   
  14. typedef struct huffman_code_tag //码字数据类型  
  15. {  
  16.     unsigned long numbits; //码字长度  
  17.     /* 码字的第1到第8比特由低到高保存在bits[0]中,第9比特到第16比特保存在bits[1]中/  
  18.     unsigned char *bits;  
  19. } huffman_code;</strong>  

静态链接库

该程序文件包含两个两个工程(project),其中“Huff_run”为主工程(Win32 Console Application),其中包含程序的主函数,有“Huff_code”为库工程(Win32 Static Library)。

Huffman编码的流程

1.读入文件。

2.进行第一次扫描,统计文件中各个字符出现的频率。

3.建立huffman树。

4.将码表及其他必要信息写入输出文件。

5.第二次扫描,对源文件进行编码并输出。

Huff_code

Huffman.h

[csharp]  view plain  copy
  1. /* 
  2.  *  huffman_coder - Encode/Decode files using Huffman encoding. 
  3.  *  http://huffman.sourceforge.net 
  4.  *  Copyright (C) 2003  Douglas Ryan Richardson; Gauss Interprise, Inc 
  5.  * 
  6.  *  This library is free software; you can redistribute it and/or 
  7.  *  modify it under the terms of the GNU Lesser General Public 
  8.  *  License as published by the Free Software Foundation; either 
  9.  *  version 2.1 of the License, or (at your option) any later version. 
  10.  * 
  11.  *  This library is distributed in the hope that it will be useful, 
  12.  *  but WITHOUT ANY WARRANTY; without even the implied warranty of 
  13.  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
  14.  *  Lesser General Public License for more details. 
  15.  * 
  16.  *  You should have received a copy of the GNU Lesser General Public 
  17.  *  License along with this library; if not, write to the Free Software 
  18.  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
  19.  */  
  20.  
  21. #ifndef HUFFMAN_HUFFMAN_H  
  22. #define HUFFMAN_HUFFMAN_H  
  23.  
  24. #include <stdio.h>  
  25.   
  26. int huffman_encode_file(FILE *in, FILE *out,FILE *out_Table );//step1:changed by yzhang for huffman statistics  
  27. int huffman_decode_file(FILE *in, FILE *out);  
  28. int huffman_encode_memory(const unsigned char *bufin,  
  29.                           unsigned int bufinlen,  
  30.                           unsigned char **pbufout,  
  31.                           unsigned int *pbufoutlen);  
  32. int huffman_decode_memory(const unsigned char *bufin,  
  33.                           unsigned int bufinlen,  
  34.                           unsigned char **bufout,  
  35.                           unsigned int *pbufoutlen);  
  36.  
  37. #endif  

Huffman.c

1.从源文件中读取数据(本实验以ASCII字符流),统计每个符号发生的概率,并建立相应的树叶节点。

[csharp]  view plain  copy
  1. #define MAX_SYMBOLS 256  
  2. typedef huffman_node* SymbolFrequencies[MAX_SYMBOLS];  
[csharp]  view plain  copy
  1. <span style="font-size: 14px;">static unsigned int  
  2. get_symbol_frequencies(SymbolFrequencies *pSF, FILE *in)//统计文件中各个字符出现频率  
  3. {  
  4.     int c;  
  5.     unsigned int total_count = 0;//扫描的总信源符号数,初始化为0  
  6.   
  7. </span><span style="font-size:12px;">    /* 将所有信源符号地址初始化为NULL,使得所有字符频率为0 */  
  8.     init_frequencies(pSF);  
  9.   
  10.     /* 计算输入文件中每个符号的频率。 */  
  11.     while((c = fgetc(in)) != EOF)//挨个读取字符</span><span style="font-size: 14px;">  
  12.   </span><span style="font-size:12px;">  {  
  13.         unsigned char uc = c;//将读取的字符赋给uc  
  14.         if(!(*pSF)[uc])//如果uc不存在对应的空间,即uc是一个新的符号  
  15.             (*pSF)[uc] = new_leaf_node(uc);//产生该字符的一个新的叶节点。</span><span style="font-size: 14px;">  
  16. </span><span style="font-size:12px;">        ++(*pSF)[uc]->count;//如果uc不是一个新的字符,则当前字符出现的频数累加1  
  17.         ++total_count;//总计数值加1  
  18.     }  
  19.   
  20.     return total_count;//返回值为总计数值  
  21. }</span>  
new_leaf_node()
[csharp]  view plain  copy
  1. static huffman_node*  
  2. new_leaf_node(unsigned char symbol)/*新建一个叶节点*/  
  3. {  
  4.     huffman_node *p = (huffman_node*)malloc(sizeof(huffman_node));  
  5.     p->isLeaf = 1;//1表示是叶节点  
  6.     p->symbol = symbol;//将新的符号的值存入symbol中  
  7.     p->count = 0;//该节点的频数为初始化0  
  8.     p->parent = 0;//该节点父节点初始化为0  
  9.     return p;  
  10. }  

2. 构建霍夫曼树及生成霍夫曼码 。
[csharp]  view plain  copy
  1. static SymbolEncoder*  
  2. calculate_huffman_codes(SymbolFrequencies * pSF)  
  3. {  
  4.     unsigned int i = 0;  
  5.     unsigned int n = 0;  
  6.     huffman_node *m1 = NULL, *m2 = NULL;  
  7.     SymbolEncoder *pSE = NULL;  
  8.  
  9. #if 0  
  10.     printf("BEFORE SORT\n");  
  11.     print_freqs(pSF);  
  12. #endif  
  13.   
  14.     /* 按升序对符号频率数组进行排序 */  
  15.     qsort((*pSF), MAX_SYMBOLS, sizeof((*pSF)[0]), SFComp);//数组的起始地址,数组的元素数,每个元素的大小,比较函数的指针  
  16.     //将所有的节点按照字符概率小到大排序,可使用qsort函数对节点结构体进行排序。排序的依据是SFComp,即根据每个字符发生的概率进行排序。  
  17. #if 0     
  18.     printf("AFTER SORT\n");  
  19.     print_freqs(pSF);  
  20. #endif  
  21.   
  22.     /*得到文件出现的字符种类数   */  
  23.     for(n = 0; n < MAX_SYMBOLS && (*pSF)[n]; ++n)  
  24.         ;  
  25.   
  26.     /* 
  27.      * Construct a Huffman tree. This code is based 
  28.      * on the algorithm given in Managing Gigabytes 
  29.      * by Ian Witten et al, 2nd edition, page 34. 
  30.      * Note that this implementation uses a simple 
  31.      * count instead of probability. 
  32.      构建霍夫曼树 
  33.      */  
  34.     for(i = 0; i < n - 1; ++i)  
  35.     {  
  36.         /* 将m1和m2设置为最小概率的两个子集。 */  
  37.             m1 = (*pSF)[0];  
  38.         m2 = (*pSF)[1];  
  39.   
  40.         /* 将m1和m2替换为一个集合{m1,m2},其概率是m1和m2之和的概率。*/  
  41.   
  42.         //合并m1、m2为非叶节点,count为二者count之和    
  43.         //并将该非叶节点的左右孩子设为m1、m2    
  44.         //将左右孩子的父节点指向该非叶节点    
  45.         //将(*pSF)[0]指向该非叶节点  
  46.         (*pSF)[0] = m1->parent = m2->parent =  
  47.             new_nonleaf_node(m1->count + m2->count, m1, m2);//  
  48.         (*pSF)[1] = NULL;//1节点置空      
  49.         /* 由于最小的两个频率数,进行了合并,频率大小发生改变,所以重新排序 */  
  50.         qsort((*pSF), n, sizeof((*pSF)[0]), SFComp);  
  51.     }  
  52.   
  53.     /* Build the SymbolEncoder array from the tree. */  
  54.     pSE = (SymbolEncoder*)malloc(sizeof(SymbolEncoder));  
  55.     //定义一个指针数组,数组中每个元素是指向码节点的指针  
  56.     memset(pSE, 0, sizeof(SymbolEncoder));  
  57.     build_symbol_encoder((*pSF)[0], pSE);  
  58.     return pSE;  
  59. }  

其中qsort函数使用到的比较函数SFComp代码如下:

[csharp]  view plain  copy
  1. static int  
  2. SFComp(const void *p1, const void *p2)  
  3. {  
  4.     const huffman_node *hn1 = *(const huffman_node**)p1;  
  5.     const huffman_node *hn2 = *(const huffman_node**)p2;  
  6.   
  7.     /* 用于将所有NULL排到最后 */  
  8.     if(hn1 == NULL && hn2 == NULL)  
  9.         return 0;//若两者都为空,则返回相等  
  10.     if(hn1 == NULL)  
  11.         return 1;//若返回值为1,大于0,则hn1排到hn2后  
  12.     if(hn2 == NULL)  
  13.         return -1;若返回值为-1,小于0,则hn2排到hn1后  
  14.     /*由小到大排列*/  
  15.     if(hn1->count > hn2->count)  
  16.         return 1;  
  17.     else if(hn1->count < hn2->count)  
  18.         return -1;  
  19.   
  20.     return 0;  
  21. }  
遍历递归Huffman树,对存在的每个字符计算码字
[csharp]  view plain  copy
  1. static void  
  2. build_symbol_encoder(huffman_node *subtree, SymbolEncoder *pSF)  
  3. {  
  4.     if(subtree == NULL)  
  5.         return;//判断是否是空树, 是则说明编码结束,  
  6.   
  7.     if(subtree->isLeaf)//判断是否为树叶节点,是则产生新的码字  
  8.         (*pSF)[subtree->symbol] = new_code(subtree);  
  9.     else  
  10.     {//  
  11.         build_symbol_encoder(subtree->zero, pSF);//遍历左子树,调用build_symbol_encoder函数自身  
  12.         build_symbol_encoder(subtree->one, pSF);//遍历右子数  
  13.     }  
  14. }  

对每个树叶节点进行编码:

[csharp]  view plain  copy
  1. static huffman_code*  
  2. new_code(const huffman_node* leaf)  
  3. {  
  4.     /* 通过走到根节点然后反转位来构建huffman代码, 
  5.     因为霍夫曼代码是通过走下树来计算的。*/  
  6.     //采用向上回溯的方法  
  7.     unsigned long numbits = 0;//表示码长,以位为单位  
  8.     unsigned char* bits = NULL;//表示指向码字的指针  
  9.     huffman_code *p;  
  10.   
  11.     while(leaf && leaf->parent)//用来判断节点和父节点是否存在,leaf为NULL时,不进行编码;parent为NULL时,已经到达树根不在编码  
  12.     {  
  13.         huffman_node *parent = leaf->parent;  
  14.         unsigned char cur_bit = (unsigned char)(numbits % 8);//current_bit为当前在bits[]的第几位  
  15.   
  16.         unsigned long cur_byte = numbits / 8;//current_byte  
  17.   
  18.         /* 如果码字长度超过一个字节,那么就在分配一个字节 */  
  19.         if(cur_bit == 0)  
  20.         {  
  21.             size_t newSize = cur_byte + 1;  
  22.             bits = (char*)realloc(bits, newSize);  
  23.             /*realloc()函数先判断当前的指针是否有足够的连续空间,如果有,扩大bits指向的地址,并且将bits返回,如果空间不够,先按照newsize指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来bits所指内存区域(注意:原来指针是自动释放,不需要使用free),同时返回新分配的内存区域的首地址。即重新分配存储器块的地址。*/  
  24.             bits[newSize - 1] = 0; /* Initialize the new byte. */  
  25.         }  
  26.   
  27. //如果是左孩子,则不用改变数值,因为初始化为0。如果是右孩子,则将该位置1  
  28.         if(leaf == parent->one)  
  29.             bits[cur_byte] |= 1 << cur_bit;//将1左移至cur_bit,再将其与bits[cur_byte]进行或的操作  
  30.   
  31.         ++numbits;//码字位数加1  
  32.         leaf = parent;//下一位的码字在当前码字的父节点一级  
  33.     }  
  34.   
  35.     if(bits)//将现有的码字进行反转  
  36.         reverse_bits(bits, numbits);  
  37.   
  38.     p = (huffman_code*)malloc(sizeof(huffman_code));  
  39.     p->numbits = numbits;//码长赋给节点的numbits  
  40.     p->bits = bits;//码字付给节点的bits  
  41.     return p;//返回值为码字  
  42. }  

码字逆序:
[csharp]  view plain  copy
  1. static void  
  2. reverse_bits(unsigned char* bits, unsigned long numbits)  
  3. {  
  4.     unsigned long numbytes = numbytes_from_numbits(numbits);//将numbits除8后上取整得到numbytes  
  5.     unsigned char *tmp =  
  6.         (unsigned char*)alloca(numbytes);//alloca()是内存分配函数,在栈上申请空间,用完后马上就释放  
  7.     unsigned long curbit;  
  8.     long curbyte = 0;//记录即将要反转的二进制码所在的的数组下标  
  9.   
  10.     memset(tmp, 0, numbytes); //将数组tmp[numbytes]所有元素置为为0  
  11.   
  12.     for(curbit = 0; curbit < numbits; ++curbit)  
  13.     {  
  14.         unsigned int bitpos = curbit % 8;//表示curbit不是8的倍数时需要左移的位数  
  15.   
  16.         if(curbit > 0 && curbit % 8 == 0)//curbit为8的倍数时,进入下一个字节  
  17.             ++curbyte;  
  18.   
  19.         tmp[curbyte] |= (get_bit(bits, numbits - curbit - 1) << bitpos);  
  20.     }  
  21.   
  22.     memcpy(bits, tmp, numbytes);//将tmp临时数组内容拷贝到bits数组中  
  23. }  

将码表写入文件

[csharp]  view plain  copy
  1. static int  
  2. write_code_table(FILE* out, SymbolEncoder *se, unsigned int symbol_count)  
  3. {  
  4.     unsigned long i, count = 0;  
  5.   
  6.     /* 计算se中的字符种类数. */  
  7.     for(i = 0; i < MAX_SYMBOLS; ++i)  
  8.     {  
  9.         if((*se)[i])  
  10.             ++count;  
  11.     }  
  12.   
  13.     /* Write the number of entries in network byte order. */  
  14.     i = htonl(count);    //在网络传输中,采用big-endian序,对于0x0A0B0C0D ,传输顺序就是0A 0B 0C 0D ,  
  15.     //因此big-endian作为network byte order,little-endian作为host byte order。  
  16.     //little-endian的优势在于unsigned char/short/int/long类型转换时,存储位置无需改变  
  17.     if(fwrite(&i, sizeof(i), 1, out) != 1)  
  18.         return 1;//将字符种类的个数写入文件  
  19.   
  20.     /* Write the number of bytes that will be encoded. */  
  21.     symbol_count = htonl(symbol_count);  
  22.     if(fwrite(&symbol_count, sizeof(symbol_count), 1, out) != 1)  
  23.         return 1;//将字符数写入文件  
  24.   
  25.     /* Write the entries. */  
  26.     for(i = 0; i < MAX_SYMBOLS; ++i)  
  27.     {  
  28.         huffman_code *p = (*se)[i];  
  29.         if(p)  
  30.         {  
  31.             unsigned int numbytes;  
  32.             /* 写入1字节的符号 */  
  33.             fputc((unsigned char)i, out);  
  34.             /* 写入一字节的码长 */  
  35.             fputc(p->numbits, out);  
  36.             /* 写入numbytes字节的码字*/  
  37.             numbytes = numbytes_from_numbits(p->numbits);  
  38.             if(fwrite(p->bits, 1, numbytes, out) != numbytes)  
  39.                 return 1;  
  40.         }  
  41.     }  
  42.   
  43.     return 0;  
  44. }  

第二次扫描 对文件进行Huffman编码

[csharp]  view plain  copy
  1. static int  
  2. do_file_encode(FILE* in, FILE* out, SymbolEncoder *se)  
  3. {  
  4.     unsigned char curbyte = 0;  
  5.     unsigned char curbit = 0;  
  6.     int c;  
  7.   
  8.     while((c = fgetc(in)) != EOF)//遍历文件的每一个字符  
  9.     {  
  10.         unsigned char uc = (unsigned char)c;  
  11.         huffman_code *code = (*se)[uc];//查表  
  12.         unsigned long i;  
  13.         /*将码字写入文件*/  
  14.         for(i = 0; i < code->numbits; ++i)  
  15.         {  
  16.             /* Add the current bit to curbyte. */  
  17.             curbyte |= get_bit(code->bits, i) << curbit;  
  18.   
  19.             /* If this byte is filled up then write it 
  20.              * out and reset the curbit and curbyte. */  
  21.             if(++curbit == 8)  
  22.             {  
  23.                 fputc(curbyte, out);  
  24.                 curbyte = 0;  
  25.                 curbit = 0;  
  26.             }  
  27.         }  
  28.     }  
输出统计结果

[csharp]  view plain  copy
  1. int huffST_getSymFrequencies(SymbolFrequencies *SF, huffman_stat *st,int total_count)  
  2. {  
  3.     int i,count =0;  
  4.     for(i = 0; i < MAX_SYMBOLS; ++i)  
  5.     {     
  6.         if((*SF)[i])  
  7.         {  
  8.             st->freq[i]=(float)(*SF)[i]->count/total_count;  
  9.             count+=(*SF)[i]->count;  
  10.         }  
  11.         else   
  12.         {  
  13.             st->freq[i]= 0;  
  14.         }  
  15.     }  
  16.     if(count==total_count)  
  17.         return 1;  
  18.     else  
  19.         return 0;  
  20. }  
  21.   
  22. int huffST_getcodeword(SymbolEncoder *se, huffman_stat *st)  
  23. {  
  24.     unsigned long i,j;  
  25.   
  26.     for(i = 0; i < MAX_SYMBOLS; ++i)  
  27.     {  
  28.         huffman_code *p = (*se)[i];  
  29.         if(p)  
  30.         {  
  31.             unsigned int numbytes;  
  32.             st->numbits[i] = p->numbits;  
  33.             numbytes = numbytes_from_numbits(p->numbits);  
  34.             for (j=0;j<numbytes;j++)  
  35.                 st->bits[i][j] = p->bits[j];  
  36.         }  
  37.         else  
  38.             st->numbits[i] =0;  
  39.     }  
  40.   
  41.     return 0;  
  42. }  
  43.   
  44. void output_huffman_statistics(huffman_stat *st,FILE *out_Table)  
  45. {  
  46.     int i,j;  
  47.     unsigned char c;  
  48.     fprintf(out_Table,"symbol\t   freq\t   codelength\t   code\n");  
  49.     for(i = 0; i < MAX_SYMBOLS; ++i)  
  50.     {     
  51.         fprintf(out_Table,"%d\t   ",i);  
  52.         fprintf(out_Table,"%f\t   ",st->freq[i]);  
  53.         fprintf(out_Table,"%d\t    ",st->numbits[i]);  
  54.         if(st->numbits[i])  
  55.         {  
  56.             for(j = 0; j < st->numbits[i]; ++j)  
  57.             {  
  58.                 c =get_bit(st->bits[i], j);  
  59.                 fprintf(out_Table,"%d",c);  
  60.             }  
  61.         }  
  62.         fprintf(out_Table,"\n");  
  63.     }  
  64. }  

各样本文件的概率分布图















实验结果

根据香农第一定理(无失真信源编码定理),对于二进制码信源符号,平均码长的下界为信源熵。当信源符号接近等概分布时,信源熵最大,而平均码长也没有可降低的空间了。故当文件的概率分布越不均匀,通过霍夫曼编码得到的编码效率越高。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值