霍夫曼编码.docx
- 文档编号:26642005
- 上传时间:2023-06-21
- 格式:DOCX
- 页数:28
- 大小:174.04KB
霍夫曼编码.docx
《霍夫曼编码.docx》由会员分享,可在线阅读,更多相关《霍夫曼编码.docx(28页珍藏版)》请在冰豆网上搜索。
霍夫曼编码
摘要
内容:
霍夫曼编码是可变字长编码(VLC)的一种。
在变字长编码中,如果码字长度严格按照对应符号出现的概率大小逆序排列,则其平均码字长度为最小。
霍夫曼编码用于数据的无损压缩,通过构建霍夫曼树,然后建立起一张特殊的霍夫曼编码表将元字符进行编码。
它是根据每一个源字符出现的估算概率,即权重而建立起来的,就是出现概率高的字符使用较短的编码,反之出现概率低的则使用较长的编码,这便使编码之后的字符串的平均期望长度降低,从而达到无损压缩数据的目的,同时保持编码的惟一可解性。
霍夫曼编码并不是唯一的,当权重相同的时候,不同的编码方式会产生不同的码。
关键词:
霍夫曼编码霍夫曼树霍夫曼编码表
目录
任务书一1
摘要2
目录3
一、课程设计内容4
二、课程设计目的4
三、环境需求4
四、Huffman编码的基本原理4
五、问题回答9
六、实验结果…………………………………………………………………………………….10
七、实验心得体会11
八、附录(参考文献、源程序)11
一、课程设计内容
利用C语言实现完整的Huffman数据压缩编码和解码。
二、课程设计目的
(1)加深对数据结构相关算法的理解,综合运用C语言编写信源编码程序。
(2)理解Huffman编码和解码的特点,掌握数据压缩的基本原理,并能够用C语言编程实现。
(3)掌握结构化分析,设计与编程方法,学会编写动态链接库程序。
三、环境需求
VisualC++6.0以上编程环境。
四、Huffman编码和解码的基本原理
1、Huffman算法
Huffman算法关键是建立Huffman树。
建立Huffman树的过程很简单,各个符号作为由二叉树连接的一串叶结点展开,每个结点有一个权,它只是符号出现的频率或概率。
然后按下列步骤建立Huffman树:
(1)确定权最低的两个自由结点。
(2)建立这两个结点的父结点,赋给它的权等于这两个结点的权之和。
(3)父结点被增加到自由结点的序列中,而两个子结点则从序列中去掉。
(4)每对组合中将权值较小的边(左子树)指定为0,权值较大的边(右子树)指
定为1。
(理论上可以任意指定,但为了易于编程,需要指定二叉树中左子树为
0,右子树为1,或者相反)。
(5)重复前面的步骤,直到只剩下一个自由结点,这个自由结点作为树的根。
Huffman树建立完毕后,将从根结点出发到叶子结点的路径上各左、右分支的编码顺序排列,最后得到该叶子结点所对应的字符二进制唯一可译编码,该编码称为Huffman编码(霍夫曼编码)。
定义5:
若对有限长的信源输出序列,相应的码字序列彼此都可无疑义地区分,就称做是唯一可译的。
表1中的码C和码D都是唯一可译码的例子,码A和码B则不是唯一可译的。
定义6:
若码中任何一个码字都不是另一个码字的字头,则称这种码为异字头码或异前缀码。
Huffman编码就是异字头码,可以证明,对任一唯一可译码必可找到码字长度相同的异字头码。
表1中的码C为异字头码,而码D则不是。
显然,异字头条件保证这类码是唯一可译的。
可以证明,虽然异字头码只是唯一可译码中的一类,但是它既易于构造,又具有普遍意义,即任何一种唯一可译码都可找到相应的、同样有效的异字头码。
异字头码的另一特点是译码具有即时性,即在接收过程中,观察一段数字序列,若是某个码字时就可进行判决,无需等待对后续码字的观察,因而没有译码延迟。
表1中对码D译码时,必须在观察到下一码字的第一个符号(每个码字以0开头,起同步作用)之后才能做出判决,而对码C的译码就不必这样做。
2、熵的定义及性质
定义1:
对于有K个事件的离散集X,若各事件的概率为P1,P2,……,PK,则集X的熵可写成
(1)
已知:
=1,
在信息科学中,离散集X通常表示某个字符集合,则熵表示集合中字符出现的平均不确定性(观察之前),或者表示已经出现的字符所给出的平均信息量(观察之后),或者表示一种惊奇性度量(正在观察)。
熵的单位是bit/字符。
定义2:
某信源平均码长的定义为:
(2)
其中
表示某个字符出现的概率,
表示某个字符的编码(所谓编码就是将不同的消息用不同的码字来表示,或为从消息集到码字的一种映射)。
定理1:
对于有K个事件的离散集X(即信源),每个事件等概率出现,即服从均匀
分布时,熵值最大,此性质称为最大离散熵定理,数学表达式如下:
(3)信源X所含有的冗余度,就是最大熵与熵之间的差值,即:
(4)
由定理1可知,只要信源不是等概率分布,就存在数据压缩的可能性。
定义3:
在数据压缩技术中,一般将压缩前每个信源符号的编码比特数(例如信源有K个不同字符)与压缩后平均每符号的编码比特数之比,定义为数据压缩比,简记为CR
(5)
定理2:
对于符号熵为H(X)的离散无记忆信源(见定义4)进行M进制不等长编码,一定存在一种无失真编码方法,其码字平均长度l满足:
(6)
当M=2时(二进制),有:
(7)
可见,H(X)就是离散无记忆信源进行无失真编码的基本极限,由定义3式(5)可知,
最大数据压缩比为:
(8)
编码效率为:
因此数据压缩的基本途径之一,就是设法改变信源的概率分布,使其尽可能地非均匀,再用最佳编码方法使平均码长l逼近信源的熵。
3、Huffman编码流程及相关函数
(1)、二叉树数据类型定义
Huffman编码和解码所使用的主要数据结构是二叉树,数据类型定义如下:
typedefstructtree_node
{
doubleweight;//权重,即概率
intflag;//标识是否为待构建结点,是用0表示,否则用1表示
intparent;//父结点
intLchild;//左结点
intRchild;//右结点
}NODE;
NODEnodes[2*257-1];
初始时,所有结点均为待构建结点,当父结点被增加到自由结点的序列中,而两个
子结点通过使对应的flag=1,成为无效结点,即从序列中去掉(见4.2节建立Huffman
树步骤(3))。
有关内容参见build_tree函数。
(2)、字符编码数据类型定义
定义如下结构体类型,用来保存每个字符的编码。
typedefstructcodetype
{
intcode[256];
intcode_length;//编码长度
}CODE;
CODEcodes[257];//定义数组
在C语言中,文件可看作由ASCII码字节流组成,每个字节占据8位,即每个ASCII
码字符由8位二进制组成,总计有28=256种字符,通过Huffman树可对每个字符进行
编码,例如若字符A(A所对应的ASCII码是65)对应的编码为110,则最后保存形式
为:
codes[65].code[0]=1,codes[65].code[1]=1,codes[65].code[2]=0,codes[65].
code_length=3
有关内容参见convert_tree_to_code函数。
3、编码主函数
Huffman编码主函数及注释如下:
intmain(intargc,char*argv[])
{
FILE*input,*output;//输入和输出文件的指针。
CODEcodes[257];//共257个字符,假设存在一个结束符,其ASCII码为257。
NODEnodes[2*257-1];//共257个字符(叶子),所以存在2*257-1=513个结点。
printf("\nCompressing%sto%s\n",argv[1],argv[2]);
input=read_file(argv[1]);//打开原始文件,读文件。
output=write_file(argv[2]);//创建一个新文件,准备写入。
InitHuffman(nodes,257);//初始化Huffman树结点。
count_bytes(input,nodes);//计算每个ASCII码出现的次数,即求权重。
scale_counts(nodes);//对权重进行伸缩,使最大权重max'不超过255。
output_counts(output,nodes);//把每个字符的权值输出到输出文件中。
build_tree(nodes);//构建huffman树。
convert_tree_to_code(codes,nodes);//把huffman树转化成二进制代码,即编码。
if(argc>3&&strcmp(argv[3],"-d")==0)
printf_model(nodes,codes);//若有第3个参数"-d",则打印输出编码模型。
compress_data(input,output,codes);//对文件进行数据压缩。
print_ratios(input,output);//输出压缩比。
printf("compressionfinish\n");
return0;
}
下面按照函数的调用次序,讨论Huffman编码所需的函数功能。
(4)、read_file函数
函数声明:
FILE*read_file(char*p);
函数功能:
使用"rb"参数打开原始待压缩文件,并返回指向待压缩文件的指针。
(5)、write_file函数
函数声明:
FILE*write_file(char*p);
函数功能:
使用"wb"参数创建已压缩目标文件,并返回指向目标文件的指针。
(6)、InitHuffman函数
函数声明:
voidInitHuffman(NODEnodes[],intn);
函数功能:
初始化二叉树结点,把每个结点的flag成员置0,parent成员置0,weight
成员置0,Lchild成员置-1,Rchild成员置-1,总计有2×n-1个结点。
(7)、count_bytes函数
函数声明:
voidcount_bytes(FILE*input,NODEnodes[]);
函数功能:
统计文件中每个字符出现的次数(对应于叶子结点权重),等价于求出
信源的概率分布,对于结束符,则假设其出现的次数为1。
(8)、scale_counts函数
函数声明:
voidscale_counts(NODEnodes[]);
函数功能:
对叶子结点权重进行同比例缩小,使缩小后的权值≤255。
对权重进行同比例缩小的目的在于:
(1)限制Huffman代码的最大长度;
(2)
减小保存权值所需的字节数,使每个结点的权值用1个字节即可保存。
(9)、output_counts函数
函数声明:
voidoutput_counts(FILE*output,NODEnodes[]);
函数功能:
Huffman编码时需要建立Huffman树,Huffman解码时仍然需要Huffman
树,若把Huffman树保存到已压缩文件中,无疑会增加已压缩文件的长度,因此为了在
解码时能够得到和编码时相同的Huffman树,可以把权重写入已压缩文件中。
可以利用
fputc函数写入257个叶子结点的权重,每个权重占用1个字节。
(10)、build_tree函数(关键函数)
函数声明:
intbuild_tree(NODEnodes[]);
函数功能:
对nodes数组赋值,构建Huffman树,并返回Huffman树的根结点位置。
说明:
257个叶子结点中weight=0的结点不参与Huffman树构建。
首先从0,1,……256各节点中找出weight不等于0且flag不等于1权值最小的两个节点,假设这两个节点分别是第7和第124结点,权值为nodes[7].weight=8,nodes[124].weight=12,下一次循环,将从0,1,……257个结点中找出weight不等于0且flag不等于1权值最小的两个结点参与运算,这2个结点的双亲为258.以此类推,直到最后只有1个根节点,返回根节点的位置。
(11)、convert_tree_to_code函数(关键函数)
函数声明:
voidconvert_tree_to_code(CODEcodes[],NODEnodes[]);
函数功能:
对codes数组赋值,完成Huffman编码。
(12)、printf_model函数
函数声明:
voidprintf_model(NODEnodes[],CODEcodes[]);
函数功能:
依次输出nodes数组0,1,......,256个叶子结点中weight≠0的叶子结点
编码,即输出符号编码。
(13)、compress_data函数(关键函数,难点)
函数声明:
voidcompress_data(FILE*input,FILE*output,CODEcodes[]);
函数功能:
按字节依次读入待压缩文件字符,并参照codes数组中的编码值,把每
个字符的编码写入输出文件中。
(14)、OutputBits函数(关键函数,难点)
函数声明:
voidOutputBits(FILE*output,CODEcodes,int*pbyte_length,
unsignedchar*pascii_code);
函数功能:
compress_data函数中调用此函数,把codes表示的编码写入output所指
向的输出文件中。
(15)、print_ratios函数
函数声明:
voidprint_ratios(FILE*input,FILE*output);
函数功能:
利用fseek和ftell函数求出待压缩文件和输出文件的文件大小。
然后求
出压缩比。
五、问题回答
(1)给定已压缩文件(见电子版),请用同伴的解码程序还原成*.doc格式,还原后,word文件的内容是什么?
答:
3.out文件解压还原后为:
被遗忘的时光蔡琴。
(2)对于给定的待压缩文件,Huffman编码是否唯一?
为什么?
答:
对于给定的待压缩文件,Huffman编码不是唯一的。
当文件中有多个字符出现的概率相同时它们的编码就有多种不同的编码方式。
即它们的Huffman树与编码有多种表达方式。
(3)Huffman编码是否能进行无限次数压缩,为什么?
例如对于原始文件k.doc,第1次压缩成kdoc1.out,;然后把kdoc1.out看成待压缩文件,进行第2次压缩,生成压缩文件kdoc2.out;然后把kdoc2.out看成待压缩文件,进行第3次压缩,生成压缩文件kdoc3.out......。
请画出原始文件和每次压缩后的ASCII码字符概率分布图。
答:
Huffman编码不能进行无限次数压缩,随着压缩次数的增多,各种ASCII码字符出现的概率会趋向于相等的趋势,这样ASCII离散集的熵反而增大了,也就失去了压缩的意义。
可通过我的程序验证,从第二次压缩ASCII码值开始逐渐见笑,压缩比也变得不稳定。
(4)自己编写一个求素数函数,把它书写成一个动态链接库形式,并在主函数中调用它。
尝试把自己编写的Huffman编码程序写成动态链接库和静态链接库形式(无需上交),并比较以下三种EXE文件的大小。
A:
调用静态链接库生成的EXE执行文件。
B:
调用动态链接库生成的EXE执行文件。
C:
直接调用函数生成的EXE执行文件。
答:
D2:
调用动态链接库生成的EXE执行文件的大小为:
168KB
D3:
直接调用函数生成的EXE执行文件的大小为:
196KB
5:
假设简单马尔可夫信源A={a,b,c},某符号输出与它之前的1个字母相关,信源输出序列为:
a,b,b,c,c,b,a,a,b,b,b,c,c,c,c,a,b,a,b,a,b,c,a,b,a,b
总计26个字符,因为总计只有3个字母,所以未压缩时,每个字母由2bit组成,回答下列问题:
未压缩时,求序列总计的编码长度。
采用通常的Huffman编码(也叫0次序Huffman编码)压缩后,求序列总计的编码长度。
若考虑某符号输出与它之前的1个字母相关,这在日常中是经常见到的,例如英语字母q之后出现字母u的概率接近于1,而其它字母出现的概率近似于0。
若采用这种Huffman编码(也叫1次序Huffman编码)压缩后,求序列总计的编码长度。
(提示:
此时是否只有一棵Huffman树?
)
以上3种编码长度是否相同,为什么?
答:
(1):
简单马尔科夫信源A={a,b,c}未压缩时序列总计的编码长度是52bit。
(2):
采用通常的Huffman编码压缩后,序列总计编码长度为41bit。
(3):
36bit——3个状态下3个树
(4):
以上三种长度不同。
E1采用平均长度编码方式,即每个字符的长度都为2bit,它的熵值最大。
通常的Huffman编码采用的是根据统计字符出现的概率,按字符出现的概率的大小进行编码,出现概率大的字符的编码长度短,出现概率小的字符的编码长度长。
1次序Huffman编码是采用简单马尔科夫编码方式进行编码,是有限记忆信源。
六、实验结果
七、实验心得体会
本次计算机软件课程设计我学到很多很多的知识,也对C语言编程有了更加深入的了解。
本程序能够实现所有老师要求的功能:
将一个.doc文件压缩成.out文件,并且可以对同一个文件进行多次压缩。
在课程设计的过程中,我查阅了很多版本的c语言和结构体相关的程序,我所编写的代码中是我通过感悟结合老师的提示程序编写出来的。
在编程的过程中也遇到了不少问题,例如:
1、读取文件之前在学习C语言的时候,老师并没有过多的涉及,此次通过查阅相关书籍,我已经掌握了这部分的知识。
2、刚开始时,关于霍夫曼编码中创建霍夫曼树的过程及其原理并不是十分了解,通过仔细研究老师给的材料和自己查看一些参考书,明白其中的原理,此算法的思想是:
(1)设给定的一组权值为{W1,W2,W3,……Wn},据此生成森林F={T1,T2,T3,……Tn},F中的每棵二叉树只有一个带权为Wi的根节点(i=1,2,……n)。
(2)在F中选取两棵根节点的权值最小和次小的二叉树作为左右构造一棵新的二叉树,新二叉树根节点的权值为其左、右子树根节点的权值之和。
(3)在F中删除这两棵最小和次小的二叉树,同时将新生成的二叉树并入森林中。
(4)重复
(2)(3)过程直到F中只有一棵二叉树为止。
霍夫曼树的应用非常广,在不同的应用中叶子节点的权值可以作不同的解释。
霍夫曼树应用于信息编码中,权值可以看成某个符号出现的频率;应用到判定过程中,权值可以看成某类数据出现的频率;应用到排序过程中,权值可以看成是已排好次序而等待合并的序列长度等。
八、附录
参考文献
[1]谭浩强.C程序设计(第二版)[M].北京:
清华大学出版社,1999..
[2]孟彩霞.计算机软件基础[M].陕西:
西安电子科技大学出版社,2003.
[3]杨晓光,数据结构实例教程北京:
清华大学出版社,北京交通大学出版社,2008
源代码
//huff_C_exp.cpp:
Definestheentrypointfortheconsoleapplication.
#include"stdio.h"
#include"stdlib.h"//exit
#include"math.h"//pow,floor函数
#include"string.h"//strcmp函数
#defineEND_OF_STREAM256//最后结束符为257
#defineMax_code_length256//共257个字符,可能的最长代码二进制位数为256个,假设第257个字符为结束符
#defineMax_number257//加上结束符,共257个字
typedefstructtree_node
{
doubleweight;//权重,把权重写入输出文件时,
intflag;//标识是否为待构建结点,是的话用0表示,否则用1表示
intparent;//父结点
intlchild;//左结点
intrchild;//右结点
}NODE;
typedefstructcodetype
{
intcode[Max_code_length];//共257个字符,可能的最长代码二进制位数为256个,假设第257个字符为结束符
intcode_length;
}CODE;
FILE*read_file1(char*p)//读取文件
{
FILE*fp;
if(!
(fp=fopen(p,"rb")))
{
printf("打开文件失败!
\n");
exit(0);
}
return(fp);
}
FILE*write_file1(char*p)//写入文件
{
FILE*fp;
if(!
(fp=fopen(p,"wb")))
{
printf("打开文件失败!
\n");
exit(0);
}
return(fp);
}
voidInitHuffman1(NODEnodes[],intn)//霍夫曼树的初始化
{
inti;
for(i=0;i<2*n-1;i++)
{
nodes[i].weight=0;
nodes[i].parent=0;
nodes[i].flag=0;
nodes[i].lchild=-1;
nodes[i].rchild=-1;
}
}
voidcount_bytes1(FILE*input,NODEnodes[])//统计待压缩文件中每个字符出现的次数,求权重
{
intn;
nodes[256].weight=1;//对于结束符“\n”,假设其定义的权重为1
n=fgetc(input);//利用fgetc函数从待压缩文件中读入一个字符
while(n!
=EOF)//EndOfFile,带压缩文件也已经读取完毕
{
nodes[n].weight++;//当文件中有相同的字符时权重自动+1
n=fgetc(input);
}
fseek(input,0,0);//使用文件指针定位函数fseek(),使原始文件指针重新回到文件头
}
voidscale_counts1(NODEnodes[])/*将权重进行同比例缩小,然后重新赋给nodes[i].weight*/
{
doubleMAX_weight,less_any_weight,a;
inti;
MAX_weight=nodes[0].weight;
for(i=1;i<=256;i++)//找到结点中权重最大的赋给MAX_weight
{
if(nodes[i].weight>MAX_
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 霍夫曼 编码
![提示](https://static.bdocx.com/images/bang_tan.gif)