哈夫曼树.docx
《哈夫曼树.docx》由会员分享,可在线阅读,更多相关《哈夫曼树.docx(13页珍藏版)》请在冰豆网上搜索。
哈夫曼树
设计性综合性实验
实验课题名称:
静态查找
院系:
数统专业:
数应双学位
课程:
数据结构教师:
焦翠珍
学号:
103122001姓名:
刘纯德(组长)
任务:
哈夫曼树静态查找
学号:
103122011姓名:
张琛玉
任务:
队列实现
学号:
103122012姓名:
胡云鹏
任务:
AOE网关键路径
学号:
103122013姓名:
黄开杰
任务:
线性表实现
2011至2012学年度第一学期
实验名称:
哈夫曼树
实验性质:
设计性(*) 综合性(*)
实验器材:
PC机并装有VC++6.0环境
实验目的:
熟悉哈夫曼树,哈夫曼编码应用
实验任务:
描述哈夫曼问题,设计思路,构造哈夫曼树,求得哈夫曼编码,调试运行并进行算法分析。
实验内容、过程及结果:
一.问题描述
在数据传送时,需要将字符转换为二进制的字符串。
例如,假设要传送的电文是ABDAACDA,电文中有A、B、C、D四种字符,如果规定A、B、C和D的编码分别为00、01、10和11,则上面的电文代码为0001110000101100,总共16个二进制数。
在传送电文时,要求电文的代码尽可能的短。
如果按照每个字符进行长度不等进行编码,将出现频率高的字符采用尽可能短的编码,则电文的代码长度就会减少。
二.设计思路
哈夫曼编码常应用于数据通信中,可以利用哈夫曼树对电文进行编码,假设需要编码的字符集合为{c1,c2,…cn},相应的,字符在电文中的出现次数为{w1,w2,…wn},以字符c1,c2…cn作为叶子结点,以w1,w2,…wn为对应叶子结点的权值构造一颗二叉树,规定哈夫曼树的左孩子分支为0,右孩子分支为1,从根结点到每个叶子结点经过的分支组成的0和1序列就是结点对应的编码。
定义一个类型为HuffmanCode的变量HT,用来存放每一个叶子结点的哈夫曼编码。
初始时,将每一个叶子结点的双亲结点域、左孩子域和右孩子域初始化为0.如果有n个叶子结点,则非叶子结点有n-1个,所以总共结点数是2*n-1个。
同时也要将剩下的n-1个双亲结点域初始化为0,依次选择两个权值最小的结点,分别作为左子树结点和右子树结点,修改它们的双亲结点域,使他们指向同一个双亲结点,同时修改双亲结点的权值,使其等于两个左、右子树结点权值的和,并修改左、右孩子结点域,使其分别指向左、右孩子结点。
重复执行以上操作n-1次,即求出n-1个非叶子结点的权值。
这样就得到了一颗哈夫曼树。
三.解决问题
假设一个字符序列为{A,B,C,D},对应的权值为{1,3,6,9}。
设计一个哈夫曼树,并输出相应的哈夫曼编码。
在实现哈夫曼的算法时,为了设计的方便,利用一个二维数组实现。
需要保存字符的权重、双亲结点的位置、左孩子结点位置和右孩子结点位置,因此需要设计n行四列。
哈夫曼树
01
01
01
weight
parent
lchild
rchild
1
5
0
0
3
5
0
0
6
6
0
0
9
7
0
0
4
6
1
2
10
7
5
3
19
0
4
6
weight
parent
lchild
rchild
1
0
0
0
3
0
0
0
6
0
0
0
9
0
0
0
0
0
0
四.实现
1.功能函数设计
1、voidhfmcoding(hfmtree&HT,hfmcode&HC,intn)初始化哈夫曼树,处理InputHuffman(HuffmanHfm)函数得到的数据,按照哈夫曼规则建立2叉树。
此函数块调用了Select()函数。
2、voidSelect(hfmtree&HT,inta,int*p1,int*p2)//Select函数,选出HT树到a为止,权值最小且parent为0的2个节点
2、intmain()
主函数:
利用已建好的哈夫曼树(如不在内存,则从文件hfmtree.txt中读入)对文件中的正文进行编码,然后将结果存入文件codefile.txt中。
如果正文中没有要编码的字符,则键盘读入并存储到ToBeTran文件中。
读入ToBeTran中将要编码的内容,将编码好的哈夫曼编码存储到CodeFile中。
3、Encoding
编码功能:
对输入字符进行编码
4、Decoding
译码功能:
利用已建好的哈夫曼树将文件codefile.txt中的代码进行译码,结果存入文件textfile.dat中。
Print()打印功能函数:
输出哈夫曼树,字符,权值,以及它对应的编码。
5.主函数的简要说明,主函数主要设计的是一个分支语句,让用户挑选所实现的功能.使用链树存储,然后分别调用统计频数函数,排序函数,建立哈夫曼函数,编码函数,译码函数来实现功能。
下面是源代码:
/*包含头文件*/
#include
#include
#include
#include
#defineinfinity10000/*定义一个无限大的值*/
/*哈夫曼树类型定义*/
typedefstruct
{
unsignedintweight;
unsignedintparent,lchild,rchild;
}HTNode,*HuffmanTree;
typedefchar**HuffmanCode;/*存放哈夫曼编码*/
intMin(HuffmanTreet,intn);
voidSelect(HuffmanTree*t,intn,int*s1,int*s2);
voidHuffmanCoding(HuffmanTree*HT,HuffmanCode*HC,int*w,intn);
voidHuffmanCoding2(HuffmanTree*HT,HuffmanCode*HC,int*w,intn);
intMin(HuffmanTreet,intn)
/*返回树中n个结点中权值最小的结点序号*/
{
inti,flag;
intf=infinity;/*f为一个无限大的值*/
for(i=1;i<=n;i++)
if(t[i].weightf=t[i].weight,flag=i;
t[flag].parent=1;/*给选中的结点的双亲结点赋值1,避免再次查找该结点*/
returnflag;
}
voidSelect(HuffmanTree*t,intn,int*s1,int*s2)
/*在n个结点中选择两个权值最小的结点序号,其中s1最小,s2次小*/
{
intx;
*s1=Min(*t,n);
*s2=Min(*t,n);
if((*t)[*s1].weight>(*t)[*s2].weight)/*如果序号s1的权值大于序号s2的权值,将两者交换,使s1最小,s2次小*/
{
x=*s1;
*s1=*s2;
*s2=x;
}
}
voidHuffmanCoding(HuffmanTree*HT,HuffmanCode*HC,int*w,intn)
/*构造哈夫曼树HT,哈夫曼树的编码存放在HC中,w为n个字符的权值*/
{
intm,i,s1,s2,start;
unsignedintc,f;
HuffmanTreep;
char*cd;
if(n<=1)
return;
m=2*n-1;
*HT=(HuffmanTree)malloc((m+1)*sizeof(HTNode));/*第零个单元未用*/
for(p=*HT+1,i=1;i<=n;++i,++p,++w)/*初始化n个叶子结点*/
{
(*p).weight=*w;
(*p).parent=0;
(*p).lchild=0;
(*p).rchild=0;
}
for(;i<=m;++i,++p)/*将n-1个非叶子结点的双亲结点初始化化为0*/
(*p).parent=0;
for(i=n+1;i<=m;++i)/*构造哈夫曼树*/
{
Select(HT,i-1,&s1,&s2);/*查找树中权值最小的两个结点*/
(*HT)[s1].parent=(*HT)[s2].parent=i;
(*HT)[i].lchild=s1;
(*HT)[i].rchild=s2;
(*HT)[i].weight=(*HT)[s1].weight+(*HT)[s2].weight;
}
/*从叶子结点到根结点求每个字符的哈夫曼编码*/
*HC=(HuffmanCode)malloc((n+1)*sizeof(char*));
cd=(char*)malloc(n*sizeof(char));/*为哈夫曼编码动态分配空间*/
cd[n-1]='\0';
/*求n个叶子结点的哈夫曼编码*/
for(i=1;i<=n;i++)
{
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';
else
cd[--start]='1';
(*HC)[i]=(char*)malloc((n-start)*sizeof(char));/*为第i个字符编码分配空间*/
strcpy((*HC)[i],&cd[start]);/*将当前求出结点的哈夫曼编码复制到HC*/
}
free(cd);
}
voidmain()
{
HuffmanTreeHT;
HuffmanCodeHC;
int*w,n,i;
printf("请输入叶子结点的个数:
");
scanf("%d",&n);
w=(int*)malloc(n*sizeof(int));/*为n个结点的权值分配内存空间*/
for(i=0;i{
printf("请输入第%d个结点的权值:
",i+1);
scanf("%d",w+i);
}
HuffmanCoding(&HT,&HC,w,n);
for(i=1;i<=n;i++)
{
printf("哈夫曼编码:
");
puts(HC[i]);
}
HuffmanCoding2(&HT,&HC,w,n);
for(i=1;i<=n;i++)
{
printf("哈夫曼编码:
");
puts(HC[i]);
}
/*释放内存空间*/
for(i=1;i<=n;i++)
free(HC[i]);
free(HC);
free(HT);
}
voidHuffmanCoding2(HuffmanTree*HT,HuffmanCode*HC,int*w,intn)
/*构造哈夫曼树HT,并从根结点到叶子结点求赫夫曼编码并保存在HC中*/
{
ints1,s2,i,m;
unsignedintr,cdlen;
char*cd;
HuffmanTreep;
if(n<=1)
return;
m=2*n-1;
*HT=(HuffmanTree)malloc((m+1)*sizeof(HTNode));
for(p=*HT+1,i=1;i<=n;i++,p++,w++)
{
(*p).weight=*w;
(*p).parent=0;
(*p).lchild=0;
(*p).rchild=0;
}
for(;i<=m;++i,++p)
(*p).parent=0;
/*构造哈夫曼树HT*/
for(i=n+1;i<=m;i++)
{
Select(HT,i-1,&s1,&s2);
(*HT)[s1].parent=(*HT)[s2].parent=i;
(*HT)[i].lchild=s1;
(*HT)[i].rchild=s2;
(*HT)[i].weight=(*HT)[s1].weight+(*HT)[s2].weight;
}
/*从根结点到叶子结点求赫夫曼编码并保存在HC中*/
*HC=(HuffmanCode)malloc((n+1)*sizeof(char*));
cd=(char*)malloc(n*sizeof(char));
r=m;/*从根结点开始*/
cdlen=0;/*编码长度初始化为0*/
for(i=1;i<=m;i++)
(*HT)[i].weight=0;/*将weight域作为状态标志*/
while(r)
{
if((*HT)[r].weight==0)/*如果weight域等于零,说明左孩子结点没有遍历*/
{
(*HT)[r].weight=1;/*修改标志*/
if((*HT)[r].lchild!
=0)/*如果存在左孩子结点,则将编码置为0*/
{
r=(*HT)[r].lchild;
cd[cdlen++]='0';
}
elseif((*HT)[r].rchild==0)/*如果是叶子结点,则将当前求出的编码保存到HC中*/
{
(*HC)[r]=(char*)malloc((cdlen+1)*sizeof(char));
cd[cdlen]='\0';
strcpy((*HC)[r],cd);
}
}
elseif((*HT)[r].weight==1)/*如果已经访问过左孩子结点,则访问右孩子结点*/
{
(*HT)[r].weight=2;/*修改标志*/
if((*HT)[r].rchild!
=0)
{
r=(*HT)[r].rchild;
cd[cdlen++]='1';
}
}
else/*如果左孩子结点和右孩子结点都已经访问过,则退回到双亲结点*/
{
(*HT)[r].weight=0;
r=(*HT)[r].parent;
--cdlen;/*编码长度减1*/
}
}
free(cd);
}
五.运行与测试
运行结果:
注:
在设计不等长编码时,必须使任何一个字符的编码都不是另一个字符编码的前缀。
算法分析:
对于本程序的主要部分,Huffman树的生成和Huffman码的编译,并没有任何的创新,无论是算法还是基本框架都只是套用课本上的经典算法。
由于采用了双重循环,此算法的时间复杂度位O(n
)。
测试信息:
本次调试中出现的最大问题就是函数间参数的传递问题,为了解决这个问题我花了大量的时间。
开始时,程序明明编译通过但是运行就是得不到自己想要的结果,这个问题困惑了好长时间,后来通过调试终于发现问题的所在,原来自己在传递参数上出现了问题,被调用的函数运行完后空间就被释放,并未给主函数中的变量造成任何影响。
为了解决这个问题,我尝试了好几种办法。
首先我想到了用全局变量解决问题,尝试的结果表明这样并不能解决问题,或者说只能部分解决问题;后来,我想用C++中“引用“这一概念解决问题,结果又以失败告终;最终,我选择了用二维指针传递数据(因为要传递的数据是指针类型),最后终于达到了预期效果
当然,调试过程中还存在一些其他的小问题,不过这多数都是因为自己不够细心或者对某个小方面的忽略所造成的,不具有代表性,在这里就不一一累述。
实验总结:
通过本次数据结构的课程设计,我学习了很多在上课没懂的知识,并对求哈夫曼树及哈夫曼编码/译码的算法有了更加深刻的了解,更巩固了课堂中学习有关于哈夫曼编码的知识,真正学会一种算法了。
当求解一个算法时,不是拿到问题就不加思索地做,而是首先要先对它有个大概的了解,接着再详细地分析每一步怎么做,无论自己以前是否有处理过相似的问题,只要按照以上的步骤,必定会顺利地做出来。
这次课程设计,我在编辑中犯了不应有的错误,设计统计字符和合并时忘记应该怎样保存数据,对文件的操作也很生疏。
在不断分析后明确并改正了错误和疏漏,我的程序有了更高的质量。
实验成绩
学号
姓名
课题小组自评分数
最后得分