课程设计1.docx
《课程设计1.docx》由会员分享,可在线阅读,更多相关《课程设计1.docx(31页珍藏版)》请在冰豆网上搜索。
课程设计1
数据结构课程设计报告
题目:
哈夫曼编码的应用
学院:
信息工程学院
专业:
信息管理与信息系统
班级:
11级信管一班
姓名:
xxxxx
学号:
**********
******
设计时间:
2012-12-1~2012-12-29
课程设计任务书
一.设计任务:
建立建立最优二叉树函数
二.设计要求:
可以建立函数输入二叉树,实现赫夫曼树的编码和译码系统,重复地显示并处理编码/解码功能,直到择出为止。
三.设计期限
其主要分为三部分:
前期,中期以及后期。
前期:
2012-12-1至2012-12-10左右,主要任务是寻找材料,确定主题范围,并且确定设计主题为:
哈夫曼编码的应用。
中期:
2012-12-11至2012-12-20左右,其中我们通过了主题的确定,由组长书写了课题的开题报告。
并且,由汪胜雄代表我组做答辩解析。
后期:
2012-12-21至2012-12-29左右,由组长分配任务,合组完成编程和论文写作任务。
其中任务分配如下:
1.由汪胜雄(组长)具体确定案例和现实问题,确定问题的大体方向。
2.由我(蔡海飞)采集所需材料及有关问题的文献,具体了解我们所需解决的问题有哪些,以确定问题所覆盖的学术范围。
3.由王欢欢就此主题写出算法,并且划出问题的流程图,我们次算法各自书写算法,并将编译所得汇总。
4.由章志收集个人编译过程所出现的问题,进行梳理,进行程序的最后运行,确定最后的程序。
5.由个人自己书写论文。
前言(黑体三号居中)
随着社会的进步与发展,高兴科技的迅猛兴起引发了一种潮流,一种风尚或者也可以说这也是一种必然趋势。
人们越来越认识到科技与计算机给我们带来的便捷,生活的高速运转与生活品质的提高甚至是挑剔以达到了前所未有的程度。
这,已说明了一切。
自从Mauchly博士与他的学生发明了世界的第一台计算机ENIAC,它标志着人类历史已经进入了计算时代。
而后,美国计算先行者IBM公司的天才们推出了世界第一台家庭个人计算机后,我们的生活已经注定离不开它的参与。
而作为高科技的产物,为了掌握它我们又不得不学习各种计算机知识,而本篇论文我所介绍的正是通过VC编写出的对哈夫曼编码的应用。
哈夫曼编码隶属哈夫曼算法的一种。
最简单的编码方式是等常编码,例如,若电文是英文字母组成,采用等长的二进制编码时,每个字符用五位二进制串表示即可(2^5>26)。
在接收端,只要按五位分割进行译码就可得到对应的文字。
众所周知,字符集中的字符被使用的频率是非均匀的,例如,英文中E和T的使用较之Q和Z要频繁得多。
因此,若让使用频率高的字符的编码尽可能短,则可使得传送的电文总长缩短。
然而采用这种不等长编码可能使译码产生多义性的电文。
因此,若对某字符集进行不等长编码,则要求字符集中任一字符的编码都不是其他字符编码的前缀,这种编码叫做前缀(编)码。
显然,等长码是前缀码。
其中,哈夫曼编码的意义就是由哈夫曼树求取最优前缀码。
而我们这次课程设计,由于各方面的知识掌握有限所编写的是其中较之简单的编译码程序。
可此次却是我们这组的血汗结晶。
期待您的观览!
目录(黑体三号居中)
第1章需求分析‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥页码
1.1问题分析•••••••‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥页码
1.2基本特点•••••••••••••‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥页码
。
。
。
。
。
。
第2章总体设计‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥页码
2.1设计思想•••••••‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥页码
2.2设计内容••••••‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥页码
。
。
。
。
。
。
第3章详细设计‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥页码
。
。
。
。
。
。
第4章编码与调试‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥页码
第5章总结‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥页码
参考文献(黑体小四居左)
附录
第1章需求分析
1.1问题分析
利用哈夫曼编码进行通信可以大大提高信道利用率,缩短信息传输时间,降低传输成本。
但是,这要求在发送端通过一个编码系统能够对待传输数据预先编码,在接收端将传来的数据进行译码。
对于双工信道(即可以双向传输信息的信道),每段都需要一个完整的编/译系统。
试为这样的信息收发站写一个哈夫曼的编译码系统。
哈弗曼编码在现今的社会的应用广泛,它主要的思想是通过哈夫曼算法来求出所构建的二叉树的最优化,所得结果就是问题的最优化结果。
我小组在对哈夫曼编码的了解下对此算法做了一个简化与优化,以期达到在一个最简洁的算法中达到对哈夫曼的主题思想的简略概括。
让我们能对哈弗曼算法的接触得到一个完美的结果。
之所以我们选择“哈夫曼编码的应用”我们是从以下几方面考虑的:
1.哈夫曼编码涉及哈夫曼树即最优二叉树问题,与现实社会较贴近,有很高的实用性;2.哈夫曼编码的思想对我们的思考问题的方式有很好的借鉴作用;3.我们本身就需要通过一次实践练习来检验一个学期来我们的学习成果,它对我们来说无疑是一个很好的方式。
我们的问题不是特别的复杂,这与我们的学习有一定的关联,但我们的思想却是与其他复杂的算法基本一致。
希望能对其他人有一定的借鉴作用!
1.2基本特点
⒈凡是能载荷一定的信息量,且码字的平均长度最短,可分离的,变长的码字集合称为最佳变长码。
为此必须将概率大的信息符号编以短的码字,概率小符号编以长的码字,使得平均码字最短,为最佳编码方法。
哈弗曼码是用用概率匹配的方法进行信源编码。
它有两个显著特点:
一是哈弗曼码的编码方法保证了概率大的符号对应于短码,概率小的符号度对应于长码,充分利用了短码;二是缩减了心愿的最后两个码字总是最后一位不同,从而保证了哈弗曼码是即时码哈弗曼变长码的效率是相当高的,它可以单个信源符号编码或用L较小的信源序列编码,对编码的设计来说也是简单得多。
但是应当注意:
要达到很高的效率仍然需要按长序列计算,这样才能使平均码字长度降低。
哈夫曼编码方法得到的码并非是唯一的,造成并非唯一的原因是:
首先,每次对信源缩减时,赋予信源最后两个概率最小的符号,用0和1可以任意的,所以可以得到不同的哈夫曼码,但不会影响码字的长度。
其次,对信源进行缩减时两个概率最小的符号合并后的概率与其他信源符号的概率相同时,这两者在缩减信源中进行概率排序,其位置放置次序是可以任意的,故会得到不同的哈夫曼码此时将影响码字的长度,一般将合并的概率放在上面,这样可以获得较小的码方差。
对于多进制哈夫曼编码,为了提高编码效率,就要使长码的符号数量尽量少、概率尽量小,所以信源符号数最好满足m-(r-1)n+r,其中r为进制数,n为缩减的次数。
例如:
要进行三进制编码,那么最好信源有7个符号,第1次合并后减少2个成为5个,第2次合并后又减少2个成为3个,这样给每一步赋予三进制符号就没有浪费了。
但如果信源只有6个符号时,为了尽量减少最长码的数量,则应该在第1次合并时添置概率为零的虚拟符号1个,事实上只合并2个概率最小的符号,后面每次合并三个,就可以使得最长码的符号数量最少,也就是长码的概率最小,从而得到最高的编码效率。
⒉哈夫曼编码在实际中已有应用,但它仍然存在一些分组码所具有的缺点。
例如概率特性必须得到精确地测定,它若略有变化,还需要换码表,以及对于二元信源,常需要多个符号合起来编码,才能取得好的效果,但当合并的符号数不大时,编码效率提高不多,尤其对于相关信源,不能令人满意,而合并的符号数增大时,码表中的码字数很多,设备将越来越复杂。
当容量设定后,随着时间的增长,存储器溢出和取空的的概率都将增。
当T很大时几乎一定会溢出或损失,由此可见,对于无线长的信息,很难采用变长码而不出现错误。
一般来说,变长码只适用于有限码的传输,即送出一段信息后,信源就停止输出,例如传真机送出一张纸上的信息后停止。
对于长信息在实际使用时可把长信息分段送出,也可通过检测存储器的状态调节信源输出即发现存储器将要溢出就停止信源输出,发现存储器将要被取空就在信道上插上空闲标志,或加快信源输出。
变长码可以无失真的译码,这是理想情况。
如果这种变长码是由信道输入的,一个码子前面有一个码元错了,就可能误认为是另一个码字而断点,结果后面一系列的码字也会译错,这常称为差错的扩散。
当然也可以采用某些措施,使码元错了一段以后,能恢复正常的码字分离和译码,这一般要求在传输过程中差错很少,或者加纠错用的监督码位,但是这样一来又增加了信息率。
此外,当信源有记忆时,用单个符号编码不可能是编码效率接近于1,因此信息率只能接近一维熵H1,而H一定小于H1。
此时仍需要多个符号一起编码,才能提高编码效率。
但导致码表长,存储器多。
第二章总体设计
2.1设计思想
哈夫曼编译码系统的主要功能是先建立哈夫曼树,然后利用建好的哈夫曼树生成哈夫曼编码后进行译码。
在通信中可以采用0和1的不同排列来表示不同的字符,称为二进制编码。
而哈夫曼树在数据编码中的应用是数据的最小冗余编码问题他是数据压缩学的基础。
若每个字符出现的频率相同,则可以采用等长的二进制编码,频率不同,采用不等长的二进制编码,频率达的字符采用位数较少的编码,频率小的采用位数较多的编码。
哈夫曼编码就是一种不等长的二进制编码,而哈夫曼树是一种最优二叉树,它的编码也是一种最优编码。
在哈夫曼树中,规定往左编码为0,往右编码为1,则得到叶子节点的编码为从根结点带叶子结点中所有路径中0和1的顺序排列。
以电报通讯为例,哈夫曼编码是求得最优前缀码,而什么样的前缀码才能使得电文总长最短呢?
则有:
1.假设组成的电文字符集和为D={d1,d2,…,dn},每个字符di在电文中出现的次数ci,di对应对应的编码长度为li,则电文总长为∑ci*li;
2.求得电文的最短总长即:
min∑ci*li;
3.通过大量的电文进行统计分析,得出每个字符di出现的概率wi,则∑wi*li表示平均码长,显然平均码长越小,电文的平均总长越短。
例如:
设组成电文的字符集D及其概率分布W为:
D={a,b,c,d,e}
W={0.12,0.40,0.15,0.08,0.25}
在该字符集D上的三种不同的前缀编码见下表所示,其中编码1,编码2,编码3的平均码长分别为3,2.2,和2.15.可以证明编码3是上述给定的概率分布下最优的前缀码(即平均码长∑wi*li最小的前缀码)。
字符
概率
编码1
编码2
编码3
a
b
c
d
e
0.12
0.40
0.15
0.08
0.25
000
001
010
011
100
000
11
01
011
10
1111
0
110
1110
10
对给定的字符集D={d1,d2,…,dn}及概率分布W={w1,w2,…,wn},可以这样求得最优前缀码:
⑴用d1,…,dn作为叶结点,w1,w2,…,wn作叶结点的权,利用哈夫曼算法构造一棵带权路径长度最小的哈夫曼树;
⑵将哈夫曼树中每个分支节点的左分支标为0,右分支标为1,把从根到每个叶子的路径上的标号连接起来,作为该叶子所代表的字符的编码。
⑶由于哈夫曼算法构造的是带权路径长度最小的二叉树,因此,上述编码的平均码长也最小。
2.2设计内容
设计包含的几个方面:
①哈夫曼树的构造
假设有n个权值,则构造出的哈夫曼树有n个叶子结点。
n个权值分别为w1,w2,…,wn则哈夫曼树构造规则为:
⑴将w1,w2,…….wn,看成有n棵树的森林;
⑵在森林中选出两个根结点最小的树合并,作为一棵新树的左右子书,且新树根结点权值为左右子树根结点权值之和;
⑶从森林中删除选取的两棵树,并将新树加入森林;
⑷重复2和3步骤,直到森林中只剩一棵树为止。
②哈夫曼编码
要求电文的赫夫曼编码,必须先定义哈夫曼编码类型,根据设计要求和实际需要定义的类型如下:
typedetstruct{
charch;//存放编码的字符
charbits[N+1];//存放编码位串
intlen;//编码的长度
}CodeNode;//编码结构体类型
③代码文件的译码
在通信中,若将字符用哈夫曼编码形式发送出去,对方接收到编码后将编码还原成字符。
译码的基本思想是:
读文件中编码,并与原先生成的哈夫曼编码表比较,遇到相等时,即取出其对应的字符存入一个新串中。
(2)其主要流程图如图所示。
第三章详细设计
(1)①赫夫曼树的存储结构描述为:
#defineN50//叶子结点数
#defineM2*N-1//赫夫曼树中结点总数
typedefstruct{
intweight;//叶子结点的权值
intlchild,rchild,parent;//左右孩子及双亲指针
}HTNode;//树中结点类型
typedefHTNodeHuffmanTree[M+1];
②哈弗曼树的算法
voidCreateHT(HTNodeht[],intn)//调用输入的数组ht[],和节点数n
{
inti,k,lnode,rnode;
intmin1,min2;
for(i=0;i<2*n-1;i++)
ht[i].parent=ht[i].lchild=ht[i].rchild=-1;//所有结点的相关域置初值-1
for(i=n;i<2*n-1;i++)//构造哈夫曼树
{
min1=min2=32767;//int的范围是-32768—32767
lnode=rnode=-1;//lnode和rnode记录最小权值的两个结点位置
for(k=0;k<=i-1;k++)
{
if(ht[k].parent==-1)//只在尚未构造二叉树的结点中查找
{
if(ht[k].weight{
min2=min1;rnode=lnode;
min1=ht[k].weight;lnode=k;
}
elseif(ht[k].weight{
min2=ht[k].weight;rnode=k;
}
}
}
ht[lnode].parent=i;ht[rnode].parent=i;//两个最小节点的父节点是i
ht[i].weight=ht[lnode].weight+ht[rnode].weight;//两个最小节点的父节点权值为两个最小节点权值之和
ht[i].lchild=lnode;ht[i].rchild=rnode;//父节点的左节点和右节点
}
}
(2)哈弗曼编码
voidCreateHCode(HTNodeht[],HCodehcd[],intn)
{
inti,f,c;
HCodehc;
for(i=0;i{
hc.start=n;c=i;
f=ht[i].parent;
while(f!
=-1)//循序直到树根结点结束循环
{
if(ht[f].lchild==c)//处理左孩子结点
hc.cd[hc.start--]='0';
else//处理右孩子结点
hc.cd[hc.start--]='1';
c=f;f=ht[f].parent;
}
hc.start++;//start指向哈夫曼编码hc.cd[]中最开始字符
hcd[i]=hc;
}
}
voidDispHCode(HTNodeht[],HCodehcd[],intn)//输出哈夫曼编码的列表
{
inti,k;
printf("输出哈夫曼编码:
\n");
for(i=0;iprintf("%c:
\t",ht[i].data);
for(k=hcd[i].start;k<=n;k++)//输出所有data中数据的编码
{
printf("%c",hcd[i].cd[k]);
}
printf("\n");
}
}
voideditHCode(HTNodeht[],HCodehcd[],intn)//编码函数
{
charstring[MAXSIZE];
inti,j,k;
scanf("%s",string);//把要进行编码的字符串存入string数组中
printf("\n输出编码结果:
\n");
for(i=0;string[i]!
='#';i++)//#为终止标志
{
for(j=0;j{
if(string[i]==ht[j].data)//循环查找与输入字符相同的编号,相同的就输出这个字符的编码
{
for(k=hcd[j].start;k<=n;k++)
{
printf("%c",hcd[j].cd[k]);
}
break;//输出完成后跳出当前for循环
}
}
}
}
(3)哈弗曼译码
voiddeHCode(HTNodeht[],HCodehcd[],intn)//译码函数
{
charcode[MAXSIZE];
inti,j,l,k,m,x;
scanf("%s",code);//把要进行译码的字符串存入code数组中
while(code[0]!
='#')
for(i=0;i{
m=0;//m为想同编码个数的计数器
for(k=hcd[i].start,j=0;k<=n;k++,j++)//j为记录所存储这个字符的编码个数
{
if(code[j]==hcd[i].cd[k])//当有相同编码时m值加1
m++;
}
if(m==j)//当输入的字符串与所存储的编码字符串个数相等时则输出这个的data数据
{
printf("%c",ht[i].data);
for(x=0;code[x-1]!
='#';x++)//把已经使用过的code数组里的字符串删除
{
code[x]=code[x+j];
}
}
}
}
(4)主函数
voidmain()
{
intn=26,i;
charorz,back,flag=1;
charstr[]={'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'};//初始化
intfnum[]={186,64,13,22,32,103,21,15,47,57,1,2,32,20,57,63,15,1,48,51,80,23,8,18,1,16};//初始化
HTNodeht[M];//建立结构体
HCodehcd[N];//建立结构体
for(i=0;i{
ht[i].data=str[i];
ht[i].weight=fnum[i];
}
while(flag)//菜单函数,当flag为0时跳出循环
(5)显示部分源程序:
{
printf("欢迎使用赫夫曼编译系统\n");
printf("制作人:
汪胜雄、王欢欢、章志、蔡海飞\n");
printf("*************************************************\n");
printf("1:
显示编码\n");
printf("2:
进行编码\n");
printf("3:
进行译码\n");
printf("0:
退出\n");
printf("*************************************************\n");
printf("请输入选择的编号:
");
scanf("%c",&orz);
switch(orz)
{
case'1':
system("cls");//清屏函数
CreateHT(ht,n);
CreateHCode(ht,hcd,n);
DispHCode(ht,hcd,n);
printf("\n按任意键返回...");
getch();
system("cls");
break;
case'2':
system("cls");
printf("请输入要进行编码的字符串(以#结束,字符为小写英文字母):
\n");
CreateHT(ht,n);
CreateHCode(ht,hcd,n);
editHCode(ht,hcd,n);
printf("\n按任意键返回...");
getch();
system("cls");
break;
case'3':
system("cls");
CreateHT(ht,n);
CreateHCode(ht,hcd,n);
DispHCode(ht,hcd,n);
printf("请输入编码(以#结束):
\n");
deHCode(ht,hcd,n);
printf("\n按任意键返回...");
getch();
system("cls");
break;
case'0':
flag=0;
printf("感谢您的使用!
\n");
break;
default:
system("cls");
}
}
}
五、算法复杂度分析:
voideditHCode(HTNodeht[],HCodehcd[],intn)//编码函数
voiddeHCode(HTNodeht[],HCodehcd[],intn)//译码函数
这两个被调函数里面都用了三重循环,其他的调用函数或者主函数都是一重或二重循环,所以算法复杂度为o(n^3)。
可以看出此算法效率是比较低的,希望能够找出更好的算法来减小复杂度。
算法复杂度分析:
voideditHCode(HTNodeht[],HCodehcd[],intn)//编码函数
voiddeHCode(HTNodeht[],HCodehcd[],intn)//译码函数
这两个被调函数里面都用了三重循环,其他的调用函数或者主函数都是一重或二重循环,所以算法复杂度为o(n^3)。
可以看出此算法效率是比较低的,希望能够找出更好的算法来减小复杂度。
第四章编码与调试