Huffman编码的方法

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

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

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

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

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

[cpp] view plaincopy
  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. typedef struct huffman_code_tag //码字数据类型
  14. {
  15. unsigned long numbits; //码字长度
  16. /* 码字的第1到第8比特由低到高保存在bits[0]中,第9比特到第16比特保存在bits[1]中/
  17. unsigned char *bits;
  18. } 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 plaincopy
  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. #ifndef HUFFMAN_HUFFMAN_H
  21. #define HUFFMAN_HUFFMAN_H
  22. #include <stdio.h>
  23. int huffman_encode_file(FILE *in, FILE *out,FILE *out_Table );//step1:changed by yzhang for huffman statistics
  24. int huffman_decode_file(FILE *in, FILE *out);
  25. int huffman_encode_memory(const unsigned char *bufin,
  26. unsigned int bufinlen,
  27. unsigned char **pbufout,
  28. unsigned int *pbufoutlen);
  29. int huffman_decode_memory(const unsigned char *bufin,
  30. unsigned int bufinlen,
  31. unsigned char **bufout,
  32. unsigned int *pbufoutlen);
  33. #endif

Huffman.c

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

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

new_leaf_node()

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

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

[csharp] view plaincopy
  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. /* 用于将所有NULL排到最后 */
  7. if(hn1 == NULL && hn2 == NULL)
  8. return 0;//若两者都为空,则返回相等
  9. if(hn1 == NULL)
  10. return 1;//若返回值为1,大于0,则hn1排到hn2后
  11. if(hn2 == NULL)
  12. return -1;若返回值为-1,小于0,则hn2排到hn1后
  13. /*由小到大排列*/
  14. if(hn1->count > hn2->count)
  15. return 1;
  16. else if(hn1->count < hn2->count)
  17. return -1;
  18. return 0;
  19. }

遍历递归Huffman树,对存在的每个字符计算码字

[csharp] view plaincopy
  1. static void
  2. build_symbol_encoder(huffman_node *subtree, SymbolEncoder *pSF)
  3. {
  4. if(subtree == NULL)
  5. return;//判断是否是空树, 是则说明编码结束,
  6. if(subtree->isLeaf)//判断是否为树叶节点,是则产生新的码字
  7. (*pSF)[subtree->symbol] = new_code(subtree);
  8. else
  9. {//
  10. build_symbol_encoder(subtree->zero, pSF);//遍历左子树,调用build_symbol_encoder函数自身
  11. build_symbol_encoder(subtree->one, pSF);//遍历右子数
  12. }
  13. }

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

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

码字逆序:

[csharp] view plaincopy
  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. memset(tmp, 0, numbytes); //将数组tmp[numbytes]所有元素置为为0
  10. for(curbit = 0; curbit < numbits; ++curbit)
  11. {
  12. unsigned int bitpos = curbit % 8;//表示curbit不是8的倍数时需要左移的位数
  13. if(curbit > 0 && curbit % 8 == 0)//curbit为8的倍数时,进入下一个字节
  14. ++curbyte;
  15. tmp[curbyte] |= (get_bit(bits, numbits - curbit - 1) << bitpos);
  16. }
  17. memcpy(bits, tmp, numbytes);//将tmp临时数组内容拷贝到bits数组中
  18. }

将码表写入文件

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

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

[csharp] view plaincopy
  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. while((c = fgetc(in)) != EOF)//遍历文件的每一个字符
  8. {
  9. unsigned char uc = (unsigned char)c;
  10. huffman_code *code = (*se)[uc];//查表
  11. unsigned long i;
  12. /*将码字写入文件*/
  13. for(i = 0; i < code->numbits; ++i)
  14. {
  15. /* Add the current bit to curbyte. */
  16. curbyte |= get_bit(code->bits, i) << curbit;
  17. /* If this byte is filled up then write it
  18. * out and reset the curbit and curbyte. */
  19. if(++curbit == 8)
  20. {
  21. fputc(curbyte, out);
  22. curbyte = 0;
  23. curbit = 0;
  24. }
  25. }
  26. }

输出统计结果

[csharp] view plaincopy
  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. int huffST_getcodeword(SymbolEncoder *se, huffman_stat *st)
  22. {
  23. unsigned long i,j;
  24. for(i = 0; i < MAX_SYMBOLS; ++i)
  25. {
  26. huffman_code *p = (*se)[i];
  27. if(p)
  28. {
  29. unsigned int numbytes;
  30. st->numbits[i] = p->numbits;
  31. numbytes = numbytes_from_numbits(p->numbits);
  32. for (j=0;j<numbytes;j++)
  33. st->bits[i][j] = p->bits[j];
  34. }
  35. else
  36. st->numbits[i] =0;
  37. }
  38. return 0;
  39. }
  40. void output_huffman_statistics(huffman_stat *st,FILE *out_Table)
  41. {
  42. int i,j;
  43. unsigned char c;
  44. fprintf(out_Table,"symbol\t   freq\t   codelength\t   code\n");
  45. for(i = 0; i < MAX_SYMBOLS; ++i)
  46. {
  47. fprintf(out_Table,"%d\t   ",i);
  48. fprintf(out_Table,"%f\t   ",st->freq[i]);
  49. fprintf(out_Table,"%d\t    ",st->numbits[i]);
  50. if(st->numbits[i])
  51. {
  52. for(j = 0; j < st->numbits[i]; ++j)
  53. {
  54. c =get_bit(st->bits[i], j);
  55. fprintf(out_Table,"%d",c);
  56. }
  57. }
  58. fprintf(out_Table,"\n");
  59. }
  60. }

各样本文件的概率分布图

实验结果

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

test3_Huffman codes相关推荐

  1. RGB Color Codes Chart

    RGB Color Codes Chart RGB颜色空间 RGB颜色空间或RGB颜色系统,从红色.绿色和蓝色的组合中构造所有颜色. 红色.绿色和蓝色各使用8位,它们的整数值从0到255.这使得256 ...

  2. Brute Force STL --- UVA 146 ID Codes

     ID Codes  Problem's Link:http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&a ...

  3. WiFi Deauthenticated Reason Codes

    Code Reason Explanation 0 Reserved Normal working operation 1 Unspecific Reason We don't know what's ...

  4. 基于deep learning的快速图像检索(Deep Learning of Binary Hash Codes for Fast Image Retrieval)

     基于deep learning的快速图像检索(Deep Learning of Binary Hash Codes for Fast Image Retrieval) 2016-07-25 14 ...

  5. cvpr2019/cvpr2018/cvpr2017(Papers/Codes/Project/Paper reading)

    cvpr2019/cvpr2018/cvpr2017(Papers/Codes/Project/Paper reading) Source:https://mp.weixin.qq.com/s/SmS ...

  6. optee的error codes

    文章目录 1.TEE internal 2.TEE client 1.TEE internal (lib/libutee/include/tee_api_defines.h)* API Error C ...

  7. F110报错:Company codes **/** do not appear in proposal ***; correct

    F110自动支付的时候报错: Company codes CS19/CS19 do not appear in proposal 29.09.2015 0929A; correct Message n ...

  8. Complete Guide to Parameter Tuning in XGBoost (with codes in Python)

    Introduction If things don't go your way in predictive modeling, use XGboost.  XGBoost algorithm has ...

  9. 2. Get the codes from GIT

    Clone the code from git. Click the "GitEx Clone". Paste the url into the "Repository ...

最新文章

  1. 数组,字符串,指针,内存分配机制
  2. Java EE官方文档汇总
  3. Python零碎知识(11):assert用法
  4. 大数据技术分享:hive内部常用函数都有哪些?
  5. HBase建表高级属性,hbase应用案例看行键设计,HBase和mapreduce结合,从Hbase中读取数据、分析,写入hdfs,从hdfs中读取数据写入Hbase,协处理器和二级索引
  6. mongodb存入mysql_关于mongodb转存MySQL
  7. 根据年份-月份,获得此月份的所有日期
  8. 538. 把二叉搜索树转换为累加树
  9. java list 从0开始_Java从零开始学二十一(集合List接口)
  10. OpenCV3.4.3DNN 模块中sample-colorization、Easy-textdetector、openpose
  11. 【多题合集】【loliの模拟赛】排列组合大套餐
  12. JQuery与springmvc实现多个文件上传操作
  13. java io学习 IO流的分类
  14. window xp共享文件夹
  15. 电影《决战中途岛》中那些真实的历史人物,后来都怎样了?
  16. 开启大功率无线充电新时代
  17. Input上传多个文件
  18. lb是什么计算机术语,lb什么意思的缩写(lb单位换算方式一览)
  19. 超详细Openstack核心组件——cinder部署
  20. 问题排查 —— OLAP平台获取查询引擎连接严重耗时

热门文章

  1. 隐马尔可夫简介(转)
  2. Openstack新建云主机的流程
  3. 积分商城系统有什么优势?
  4. mfc实现计算器的平方功能_电子史 · 计算器,每天都在进化中
  5. ios如何设计图标_50 iOS图标设计,第1卷
  6. GPU服务器查询进程所属用户
  7. 贝叶斯网专题6:团树传播
  8. selenium登录识别验证码
  9. 英国跑车制造商路特斯发布其首款纯电动超跑 售价1449万元
  10. 分布式系统复习笔记2019年秋