13利用贪婪法构造哈夫曼编码1Word格式文档下载.docx
《13利用贪婪法构造哈夫曼编码1Word格式文档下载.docx》由会员分享,可在线阅读,更多相关《13利用贪婪法构造哈夫曼编码1Word格式文档下载.docx(21页珍藏版)》请在冰豆网上搜索。
![13利用贪婪法构造哈夫曼编码1Word格式文档下载.docx](https://file1.bdocx.com/fileroot1/2023-1/8/aa29531d-2f2c-41c9-9796-a834d23b14cc/aa29531d-2f2c-41c9-9796-a834d23b14cc1.gif)
5.2静态存储哈夫曼编码8
5.3动态存储选择最小结点的算法9
5.4动态存储哈夫曼编码10
6系统测试12
6.1设计测试数据12
6.2静态存储测试结果及分析12
6.3动态存储测试结果及分析13
7结论14
8参考文献14
致谢15
摘要
哈夫曼编码是哈夫曼树的一个应用。
哈夫曼树又称最优二叉树,是一种带权路径长度最短的二叉树。
哈夫曼在上世纪五十年代初就提出这种编码时,根据字符出现的概率来构造平均长度最短的编码。
它是一种变长的编码。
在编码中,若各码字长度严格按照码字所对应符号出现概率的大小的逆序排列,则编码的平均长度是最小的。
(注:
码字即为符号经哈夫曼编码后得到的编码,其长度是因符号出现的概率而不同,所以说哈夫曼编码是变长的编码。
)
哈夫曼编码(HuffmanCoding)是一种编码方式,哈夫曼编码是可变字长编码(VLC)的一种。
以哈夫曼树─即最优二叉树,带权路径长度最小的二叉树,经常应用于数据压缩。
关键词:
哈夫曼树,哈夫曼编码,最优二叉树,贪婪法
1引言
1.1问题的提出
随着计算机的普及,网络进入大众的家庭,算法在现实生活中也越来越重要,算法在现实生活中应用节约大量时间和空间,算法是计算机专业重要的一门专业基础课,也是学生最先接触到的专业课,该课程的掌握情况直接影响后继课程的深入学习以及学生软件开发能力的培养及提高。
利用哈夫曼编码进行通信可以大大提高信道利用率,缩短信息传输时间,降低传输成本,但是,这要求在发送端通过一个编码系统对待传数据预先编码,在接收端将传来的数据进行译码(复原)。
对于双工信道(即可以双向传输信息的信道),每端都需要一个完整的编/译码系统。
试为这样的信息收发站写一哈夫曼编/译码系统。
1.2国内外研究的现状
随着计算机科学的蓬勃发展,社会已进入信息时代。
电子计算机和通信网络的广泛应用,一方面为人们的工作和生活提供了很大的方便,另一方面也提出了许多亟待解决的问题,其中信息的安全性就是一个突出的问题。
因此,密码学理论和技术已成为信息科学和技术中的一个重要领域。
随着计算机网络的迅速发展,特别是近年来电子商务的兴起,现代密码学的应用已不仅仅局限于政治、军事以及外交等领域,其商用价值和社会价值也已得到了充分的肯定。
而哈夫曼编码是其中一个重要应用之一。
在一般的数据结构的书中,树的那章后面,著者一般都会介绍一下哈夫曼(HUFFMAN)树和哈夫曼编码。
哈夫曼编码是哈夫曼树的一个应用。
哈夫曼编码应用广泛,如JPEG中就应用了哈夫曼编码。
哈夫曼树又称最优二叉树,是一种带权路径长度最短的二叉树。
所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的路径长度(若根结点为0层,叶结点到根结点的路径长度为叶结点的层数)。
树的带权路径长度记为WPL=(W1*L1+W2*L2+W3*L3+...+Wn*Ln),N个权值Wi(i=1,2,...n)构成一棵有N个叶结点的二叉树,相应的叶结点的路径长度为Li(i=1,2,...n)。
可以证明哈夫曼树的WPL是最小的。
)
然而怎样构造一棵哈夫曼树呢?
最具有一般规律的构造方法就是哈夫曼算法。
一般的数据结构的书中都可以找到其描述:
一、对给定的n个权值{W1,W2,W3,...,Wi,...,Wn}构成n棵二叉树的初始集合F={T1,T2,T3,...,Ti,...,Tn},其中每棵二叉树Ti中只有一个权值为Wi的根结点,它的左右子树均为空。
(为方便在计算机上实现算法,一般还要求以Ti的权值Wi的升序排列。
二、在F中选取两棵根结点权值最小的树作为新构造的二叉树的左右子树,新二叉树的根结点的权值为其左右子树的根结点的权值之和。
三、从F中删除这两棵树,并把这棵新的二叉树同样以升序排列加入到集合F中。
四、重复二和三两步,直到集合F中只有一棵二叉树为止。
用C语言实现上述算法,可用静态的二叉树或动态的二叉树。
若用动态的二叉树可用以下数据结构:
structtree{
floatweight;
/*权值*/
union{
charleaf;
/*叶结点信息字符*/
structtree*left;
/*树的左结点*/
};
structtree*right;
/*树的右结点*/
structforest{/*F集合,以链表形式表示*/
structtree*ti;
/*F中的树*/
structforest*next;
/*下一个结点*/
例:
若字母A,B,Z,C出现的概率为:
0.71,0.54,0.28,0.43;
则相应的权值为:
75,54,28,43。
构造好哈夫曼树后,就可根据哈夫曼树进行编码。
例如:
上面的字符根据其出现的概率作为权值构造一棵哈夫曼树后,经哈夫曼编码得到的对应的码值。
只要使用同一棵哈夫曼树,就可把编码还原成原来那组字符。
显然哈夫曼编码是前缀编码,即任一个字符的编码都不是另一个字符的编码的前缀,否则,编码就不能进行翻译。
a,b,c,d的编码为:
0,10,101,11,对于编码串:
1010就可翻译为bb或ca,因为b的编码是c的编码的前缀。
刚才进行哈夫曼编码的规则是从根结点到叶结点(包含原信息)的路径,向左孩子前进编码为0,向右孩子前进编码为1,当然你也可以反过来规定。
这种编码方法是静态的哈夫曼编码,它对需要编码的数据进行两遍扫描:
第一遍统计原数据中各字符出现的频率,利用得到的频率值创建哈夫曼树,并必须把树的信息保存起来,即把字符0-255(2^8=256)的频率值以2-4BYTES的长度顺序存储起来,(用4Bytes的长度存储频率值,频率值的表示范围为0--2^32-1,这已足够表示大文件中字符出现的频率了)以便解压时创建同样的哈夫曼树进行解压;
第二遍则根据第一遍扫描得到的哈夫曼树进行编码,并把编码后得到的码字存储起来。
静态哈夫曼编码方法有一些缺点:
一、对于过短的文件进行编码的意义不大,因为光以4BYTES的长度存储哈夫曼树的信息就需1024Bytes的存储空间;
二、进行哈夫曼编码,存储编码信息时,若用与通讯网络,就会引起较大的延时;
三、对较大的文件进行编码时,频繁的磁盘读写访问会降低数据编码的速度。
因此,后来有人提出了一种动态的哈夫曼编码方法。
动态哈夫曼编码使用一棵动态变化的哈夫曼树,对第t+1个字符的编码是根据原始数据中前t个字符得到的哈夫曼树来进行的,编码和解码使用相同的初始哈夫曼树,每处理完一个字符,编码和解码使用相同的方法修改哈夫曼树,所以没有必要为解码而保存哈夫曼树的信息。
编码和解码一个字符所需的时间与该字符的编码长度成正比,所以动态哈夫曼编码可实时进行。
动态哈夫曼编码比静态哈夫曼编码复杂的多,有兴趣的读者可参考有关数据结构与算法的书籍。
前面提到的JPEG中用到了哈夫曼编码,并不是说JPEG就只用哈夫曼编码就可以了,而是一幅图片经过多个步骤后得到它的一列数值,对这些数值进行哈夫曼编码,以便存储或传输。
哈夫曼编码方法比较易懂,大家可以根据它的编码方法,自己编写哈夫曼编码和解码的程序。
1.3任务与分析
本课题主要的任务是使用贪婪法构造哈夫曼编码,就是要通过构造哈夫曼树来实现。
主要任务:
(1)给出静态存储方式下的Huffman编码算法并编程实现;
(2)给出动态存储方式下的Huffman编码算法并编程实现;
通过此课题,能加深对贪婪法的理解,具有初步的独立分析和设计能力,提高综合运用所学理论知识和方法独立分析和解决问题的能力。
2程序的主要功能
2.1定义结构体
定义一个结构体反映二叉树每个结点的包含内容,包含数据域和左右指针域
2.2动态存储
按照动态存储方式存储结点。
2.3静态存储
了解存储的长度分配存储空间,。
2.4哈夫曼算法
熟悉贪婪法,选择贪婪发进行哈夫曼编码。
第一步:
初始化n个单节点的树,并为它们标上字母表中的字符。
把每个字符的概率记在树的根中,用来指出树的权重。
第二步:
重复下面的步骤,直到只剩一棵单独的树。
找到两棵权重最小的树。
把它们作为新树中的左右子树,并把权重之和作为新的权重记录在新树的根中。
2.5选择结点编码
选择合适的算法,从中选择连个最小的结点编码。
3总体设计
主函数
哈夫曼编码
调用选择算法
输出哈夫曼编码
图1总体结构设计
3.1数据存储结构设计
(1)哈夫曼树的存储结构设计
对于哈夫曼编码问题,在构造哈夫曼树时要求能方便地实现从双亲点到左右孩子结点的操作,在进行哈夫曼编码时又要求能方便地实现从孩子到双亲结点的操作。
因此,设计哈夫曼树的存储结构为双亲孩子存储结构如图
(一)。
weight
Parent
leftChild
ringthChild
表
(一)
哈夫曼树的结点存储结构设计如下:
typedefstruct
{
intweight;
//权值
intparent;
//双亲结点下标
intleftChild;
//左孩子下标
intrightChild;
//右孩子下标
}HaffNode;
//哈夫曼树的结点结构
(2)哈夫曼编码的存储结构设计
对于存放哈夫曼编码数据元素的结构,由于哈夫曼编码是不等长编码,所以存放编码的数组必须有一个start来存储数组中码字的起始位置。
如图
(二)。
start
Bit[0]
Bit[1]
…………
Bit[MaxBit-1]
表
(二)
存放哈夫曼编码的数组元素的存储结构设计如下:
intbit[MaxN];
//数组
intstart;
//编码的起始下标值
//字符的权值
}Code;
//哈夫曼编码的结构
3.2操作模块设计
(1)此模块构造了一个函数Haffman()用来建立叶结点的个数为n权值数组为weight的哈夫曼树haffTree
(2)此模块构造了一个函数HaffmanCode()用来将n个结点的哈夫曼树haffTree构造成哈夫曼编码haffCode
(3)此模块构造了一个函数HaffmanCoding()根据每个结点的编码hcode对哈夫曼编码strcode进行译码,此函数调用了一个字符匹配函数BFIndex()。
(4)字符匹配函数BFIndex(),由BF算法改造而成,函数的功能是确认当前子串是否在母串的前部,如果是返回1否则返回0。
(5)主函数模块main()用来进行字符串的输入和各个函数模块的调用和结果的显示等。
3.3建树算法设计
(3)在给定的n个取值
构造n棵只有根结点的二叉树,从而得到一个二叉树森林
。
(4)在二叉树森林F中选取根结点的权值最小和次小的两棵二叉树作为左右树构造心的二叉树,此时,新的二叉树的根结点权值为左右子树根结点权值之和。
(5)在二叉树森林F中删除作为心二叉树左右子树的两棵二叉树,将新二叉树假如到二叉树森林F中。
(6)重复步骤
(2)和(3),当二叉树森林F中只剩下一棵二叉树时,这棵二叉树就是所构造的哈夫曼树。
4结构体的说明
静态存储结构体定义
intweight;
intparent,lchild,rchild;
}HTnode,*Huffmantree;
typedefchar**Huffmancode;
动态存储结构体定义
typedefstructnode
intparent,lchild,rchild;
intloca;
structnode*next,*father;
}node,*Huffmantree;
5模块分析
5.1静态存储选择最小结点的算法
依次遍历静态存储结构选出权值最小的两个结点;
核心代码如下:
voidselect(HuffmantreeHT,intk,int*s1,int*s2)
{//赫夫曼树HT中选paren为0且权值最小的两结点,s1,s2
inti,j=1,min,e;
while(HT[j].parent!
=0)j++;
if(j>
k){printf("
无空闲结点!
\n"
);
exit(0);
}
elsemin=HT[j].weight;
for(i=1;
i<
=k;
i++)
if((HT[i].weight<
min)&
&
(HT[i].parent==0))
{j=i;
min=HT[i].weight;
e=*s1=j;
j=1;
=0||j==*s1)
j++;
if(j>
无空闲结点!
(HT[i].parent==0)&
(i!
=e))
{j=i;
*s2=j;
5.2静态存储哈夫曼编码
选择贪婪法在静态存储方式下编制哈夫曼;
voidHuffmancoding(Huffmantree*HT,Huffmancode*HC,intw[],intn)
inti,m,c,s1,s2,start,f;
//char*cd;
Huffmantreep;
if(n<
=1){printf("
无法构成树!
m=2*n-1;
*HT=(Huffmantree)malloc((m+1)*sizeof(HTnode));
//0号单元未用
for(p=*HT+1,i=1;
=n;
i++,++p)
{(*p).weight=w[i];
printf("
HT[%d].weight=%d\n"
i,(*p).weight);
(*p).lchild=0;
(*p).parent=0;
(*p).rchild=0;
}
for(;
=m;
i++,++p)
{(*p).weight=0;
for(i=n+1;
++i)
{select(*HT,i-1,&
s1,&
s2);
(*HT)[s1].parent=i;
(*HT)[s2].parent=i;
(*HT)[i].lchild=s1;
(*HT)[i].rchild=s2;
(*HT)[i].weight=(*HT)[s1].weight+(*HT)[s2].weight;
printf("
HT[%d]andHT[%d]-->
HT[%d].weight\n"
s1,s2,i,(*HT)[i].weight);
*HC=(Huffmancode)malloc((n+1)*sizeof(char*));
char*cd;
cd=(char*)malloc(n*sizeof(char));
cd[n-1]='
\0'
;
HuffmanTreeCodeisasfollows:
"
{start=n-1;
for(c=i,f=(*HT)[i].parent;
f!
=0;
c=f,f=(*HT)[f].parent)
if((*HT)[f].lchild==c)
cd[--start]='
0'
elsecd[--start]='
1'
(*HC)[i]=(char*)malloc((n-start)*sizeof(char));
strcpy((*HC)[i],&
cd[start]);
\nHT[%d].weight=%dnode'
sHuffmancodeis:
%s"
i,(*HT)[i].weight,(*HC)[i]);
free(cd);
5.3动态存储选择最小结点的算法
依次遍历动态存储结构选出权值最小的两个结点;
voidselect(HuffmantreeHT,intk,Huffmantree*s1,Huffmantree*s2)
{
inti,j=1,min,e;
Huffmantreep,q;
p=HT;
while(p)
{if(p->
parent!
=0)
{j++;
p=p->
next;
elsebreak;
if(j>
elsemin=p->
weight;
q=HT;
{if((q->
weight<
(q->
parent==0))
min=q->
p=q;
q=q->
if(q==NULL)break;
}*s1=p;
e=j;
parent!
=0||j==e)
{j++;
elsebreak;
{if((q!
=(*s1))&
{j=i;
if(q==NULL)break;
q=q->
}*s2=p;
5.4动态存储哈夫曼编码
选择贪婪法在动态态存储方式下编制哈夫曼;
{inti,m,c,start,f;
Huffmantrees1,s2,p,q,q1;
*HT=(Huffmantree)malloc(sizeof(node));
for(p=*HT,i=1;
i++)
(*p).loca=i;
if(i<
=n-1)
{q=(Huffmantree)malloc(sizeof(node));
p->
next=q;
p=q;
p->
next=NULL;
{q1=(Huffmantree)malloc(sizeof(node));
select(*HT,i-1,&
s1->
parent=i;
s2->
q1->
lchild=s1->
loca;
q1->
rchild=s2->
loca;
loca=i;
q1->
parent=0;
weight=s1->
weight+s2->
weight;
father=q1;
s2->
next=q1;
p=q1;
printf("
HT[%d]andHT[%d]-->
HT[%d].weight\n"
s1->
loca,s2->
loca,i,p->
weight);
}p->
*HC=(Huffmancode)malloc((n+1)*sizeof(char*));
p=*HT;
q=p;
for(c=i,f=q->
parent,q1=q->
father;
c=f,f=q1->
parent,q1=q1->
father)
if(q1->
lchild==c)d[--start]='
\n%dnode'
p->
weight,(*HC)[i]);
p=p->
}free(cd);
\n"
6系统测试
6.1设计测试数据
图2
6.2静态存储测试结果及分析
当运行程序时首先跳出界面如下:
图3
输入各结点权值后运行结果如下:
图4
6.3动态存储测试结果及分析
图5
图6
7结论
通过这次实验,