C语言之霍夫曼编码学习

?
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,代码实现

  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)
----------------------------------------------------------------------------------------------------------------

时间: 2024-12-17 03:02:13

C语言之霍夫曼编码学习的相关文章

霍夫曼树及霍夫曼编码的C语言实现

从周五开始学习霍夫曼树,一直到今天终于完成,期间遇到了各种各样的棘手的问题,通过一遍遍在纸上分析每一步的具体状态得以解决.现在对学习霍夫曼树的过程加以记录 首先介绍霍夫曼树 霍夫曼树(Huffman Tree),又称最优二叉树,是一类带权路径长度最短的树.假设有n个权值{w1,w2,-,wn},如果构造一棵有n个叶子节点的二叉树,而这n个叶子节点的权值是{w1,w2,-,wn},则所构造出的带权路径长度最小的二叉树就被称为赫夫曼树. 这里补充下树的带权路径长度的概念.树的带权路径长度指树中所有叶

贪心算法-霍夫曼编码

霍夫曼编码是一种无损数据压缩算法.在计算机数据处理中,霍夫曼编码使用变长编码表对源符号(如文件中的一个字母)进行编码,其中变长编码表是通过一种评估来源符号出现机率的方法得到的,出现机率高的字母使用较短的编码,反之出现机率低的则使用较长的编码,这便使编码之后的字符串的平均长度.期望值降低,从而达到无损压缩数据的目的.例如,在英文中,e的出现机率最高,而z的出现概率则最低.当利用霍夫曼编码对一篇英文进行压缩时,e极有可能用一个比特来表示,而z则可能花去25个比特(不是26).用普通的表示方法时,每个

霍夫曼编码/译码器

赫夫曼树的应用 1.哈夫曼编码 在数据通信中,需要将传送的文字转换成二进制的字符串,用0,1码的不同排列来表示字符.例如,需传送的报文为"AFTER DATA EAR ARE ART AREA",这里用到的字符集为"A,E,R,T,F,D",各字母出现的次数为{8,4,5,3,1,1}.现要求为这些字母设计编码.要区别6个字母,最简单的二进制编码方式是等长编码,固定采用3位二进制,可分别用000.001.010.011.100.101对"A,E,R,T,F

基于python的二元霍夫曼编码译码详细设计

一.设计题目 对一幅BMP格式的灰度图像(个人证件照片)进行二元霍夫曼编码和译码 二.算法设计 (1)二元霍夫曼编码: ①:图像灰度处理: 利用python的PIL自带的灰度图像转换函数,首先将彩色图片转为灰度的bmp图像,此时每个像素点可以用单个像素点来表示. ②:二元霍夫曼编码: 程序流程图: 详细设计: 统计像素点频率,首先通过python自带的PIL库的图像像素点读取函数read()获取灰度图像的所有像素点,通过循环遍历每个像素点,将每个出现的像素点值以及其次数以键值对的形式放入到pyt

霍夫曼编码求节省空间

霍夫曼编码将频繁出现的字符采用短编码,出现频率较低的字符采用长编码.具体的操作过程为:i)以每个字符的出现频率作为关键字构建最小优先级队列:ii)取出关键字最小的两个结点生成子树,根节点的关键字为孩子节点关键字之和,并将根节点插入到最小优先级队列中,直至得到一棵最优编码树. 霍夫曼编码方案是基于______策略的.用该方案对包含a到f6个字符的文件进行编码,文件包含100000个字符,每个字符的出现频率(用百分比表示)如表1-3所示,则与固定长度编码相比,该编码方案节省了______存储空间.

霍夫曼编码

进行霍夫曼编码前,我们先创建一个霍夫曼树. ⒈将每个英文字母依照出现频率由小排到大,最小在左,如Fig.1. ? ? ⒉每个字母都代表一个终端节点(叶节点),比较F.O.R.G.E.T五个字母中每个字母的出现频率,将最小的两个字母频率相加合成一个新的节点.如Fig.2所示,发现F与O的频率最小,故相加2+3=5. ⒊比较5.R.G.E.T,发现R与G的频率最小,故相加4+4=8. ⒋比较5.8.E.T,发现5与E的频率最小,故相加5+5=10. ⒌比较8.10.T,发现8与T的频率最小,故相加8

采用霍夫曼编码(Huffman)画出字符串各字符编码的过程并求出各字符编码 --多媒体技术与应用

题目:有一个字符串:cabcedeacacdeddaaaba,问题: (1)采用霍夫曼编码画出编码的过程,并写出各字符的编码 (2)根据求得的编码,求得各编码需要的总位数 (3)求出整个字符串总编码长度,并计算出字符串位数在编码前与编码后的比值 解答: (1)各字符出现频率统计如下表所示. |符号 |出现次数 |出现频率| |--|--|--| | a |7|0.35| |b|2|0.1| |c|4|0.2| |d|4|0.2| |e|3|0.15| 编码过程如下图所示: 各字符编码如下表所示:

霍夫曼编码实现

先把代码贴了,有时间再写思路.. 二叉树定义: binaryTree.h 1 #ifndef BINARYTREE_H 2 #define BINARYTREE_H 3 #include <iostream> 4 #include "LinkedQueue.h" 5 6 template<class T> 7 class BinaryTree; 8 9 10 int count; 11 12 template<class T> 13 class Bi

优先级队列优化的霍夫曼编码(带中文压缩)

利用STL中的优先级队列进行优化 我将压缩和解压分为两部分,其实一些还是是一样的 压缩的时候通过bitset将每8个01串压缩成一个字节,如果最后一个不满足8个,用0补齐,但是要记录最后一个字节实际有多少个有效位,将其存入文件最后一个字节,解压的时候先将文件定位到最后一个字节,取出有效位的个数,压缩文件真正有效的是倒数第二个字节,倒数第一个字节只是记录倒数第二个字节中有几位是有效的,解压的时候根据密码本(记录每个字节的权值)建立哈夫曼树,然后更具哈夫曼树解压文件 压缩代码部分: #include