哈弗曼编译器实验报告
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
实习报告
题目:哈弗曼编译码器
班级:电信系通信工程0902班
完成日期:2010.11
一、需求分析
1、编写哈弗曼编译码器,其主要功能有
(1)I:初始化(Initialization)。
从终端读入字符集大小n,以及n个字符和n个权值,建立哈夫曼树。
(2)E:编码(Encoding)。
利用已建好的哈夫曼树),对从终端输入的正文进行编码,然后从终端输出。
(3)D:译码(Decoding )。
利用已建好的哈夫曼树将从终端输入的代码进行译码,结果从终端输出。
(4)P:印哈夫曼树(Print)。
将已编码的的哈夫曼树显示在终端上,同时将此字符形式的哈夫曼树。
2、测试数据:
输入的字符={a, b, c, d, e}
其对应的权值={5,29,7,8,14}
二、概要设计
1、二哈弗曼树的抽象数据类型定义为:
ADT HuffmanTree{
数据对象D:D是具有相同性质的数据元素的集合
数据关系R:
若D=Φ,则R= Φ,哈弗曼树为空
若D≠Φ,则R= {H},H是如下二元关系:
(1)在D中存在唯一的称为根的数据元素root,它在关系H下无前驱(2)若D-{root}≠Φ,则存在D-{root}={Dl,Dr}。
且Dl∩Dr=Φ
(3)若Dl≠Φ,则Dl中存在唯一的数据元素Xl,<root, Xl>属于H,且存在Dl上的关系H1属于H。
若Dr≠Φ,则Dr中存在唯一的数据元
素Xr,<root, X>属于H,且存在Dr上的关系Hr属于H
H={<root, Xl>,<root, X>,Hl,Hr};
(4)(Dl,{Hl})是一棵符合本定义的哈弗曼树,称为根的左子树。
(Dr,{Hr})是一棵符合本定义的哈弗曼树,称为根的右子树。
基本操作:
HuffmanCoding(&HT, &HC, &sum)
操作结果:建立哈弗曼树并进行编码将编码存放在HC中,并返回字符的个数。
Encoding(HT, HC, sum)
操作结果:利用已建立的哈弗曼树对字符进行编码
Decoding(HuffmanTree HT,HuffmanCode HC,int sum)
操作结果:对输入的密码进行翻译
Print(HT, HC, sum)
操作结果:打印建立好的哈弗曼树
}ADT HuffmanTree
三、详细设计
(1)哈弗曼树每个节点的定义:
typedef struct
{
unsigned int weight;
unsigned int parent,lchild,rchild;
char elemt[20];
}HTNode,*HuffmanTree;
(2)定义指向哈弗曼树的指针,用于动态分配空间
typedef char **HuffmanCode;
(3)哈弗曼树的基本操作
Void HuffmanCoding(HuffmanTree &HT, HuffmanCode &HC, int *w, int n)
{ //建立哈弗曼树,求出哈弗曼编码
if (n<=1)return;
m=2*n-1; //n 个叶子的HuffmanTree共有2n-1个结点
HT=(HuffmanTree)malloc((m+1)*sizeof(HTNode));
for(p=HT+1,i=0; i<n; ++i,++p,++w)*p={*w,0,0,0};
//给前n个单元初始化
for(;i<=m; ++i,++p)*p ={0,0,0,0}; //从叶子之后的存储单元清零for(i=n+1;i<=m; ++i)
{ /
/建Huffman树(从n个叶子后开始存内结点) Select(HT, i-1, s1, s2);
//选择parent为0且weight最小的两个结点,
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;
} //以上建立了哈弗曼树,以下求哈弗曼编码
HC=(HuffmanCode)malloc((n+1)*sizeof(char*));
//分配n个字符编码的头指针向量(一维数组)
cd=(char*) malloc(n*sizeof(char)); //分配求编码的临时最长空间
cd[n-1]=“\0”; //编码结束符(从cd[0]~cd[n-1]为合法空间)
for(i=1;i<=n;++i) //逐个字符求Huffman编码
{
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]); //从cd复制编码串到HC所指空间
}
free(cd); //释放临时空间
}//HuffmanCoding
for(i=0; 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”;
} / /从叶子到根逆向求编码
}// HuffmanCoding
Void Encoding(HuffmanTree HT,HuffmanCode HC,int sum) //利用已经建立的哈弗曼树对输入的字符进行哈弗曼编码
{
for(int i=0;a[i]!='\0';i++)
//依次判断字符的对应的哈弗曼编码
{
for(int n=0;HT[n].elemt[0];n++)
//查找a[i]在哈弗曼树中的位置
{
strcpy(p,HC[n]);p=p+strlen(HC[n]);break;
//把编码复制接到code后
}
}
i=0;
printf("得到的编码是:\n");
while(code[i]!='\0') //输出字符对应的哈弗曼编码
{
printf("%c",code[i++]);
}
}// Encoding
Void Decoding(HuffmanTree HT,HuffmanCode HC,int sum) //译码
{
while(code1[i]!='\0')
{
if(code1[i]=='0') b=HT[b].lchild;
//当遇到0时指向哈弗曼树的左子树
else if(code1[i]=='1') b=HT[b].rchild;
//当遇到1时指向哈弗曼树的右子树
}
if(HT[b].lchild==0&&HT[b].rchild==0)
//当左右子树均为空时表明已找到对应的字符
{
a1[n++]=HT[b].elemt[0];b=2*sum-2;
//将对应的字符放在数组a1中并重新设置b的值继续翻译 }
i++;
}// Decoding
Void Print(HuffmanTree HT,HuffmanCode HC,int sum)
//打印哈弗曼树
{
for(int i=0;i<2*sum-1;i++)
//从首元素开始,逐个输入哈弗曼树的各项数据
{
printf("%d%c%d%d%d",i,HT[i].elemt[0],HT[i].parent,HT[i].lch ild,HT[i].rchild);
}
}// Print
四、调试分析
1、由于书上有详细的建立哈弗曼树的算法,编码,译码,打印哈弗曼树的算法比较简单,程序的模块比较简单,所以整体的思路比较清晰,但是在将算法,写为C语言的过程中,出现了很多的语法和逻辑上的错误,所以用了很多的时间调试,修改错误。
2.本本次试验吸取上第一次试验的经验教训,注意了对挡板的设置,程序能够对出现的错误进行适当的处理,加强了程序的健壮性。
3、通过此次试验加深了对哈弗曼树的理解。
五、用户手册
1、本程序的执行文件为:hafuman12.exe。
2、进入演示程序后用户界面为:
/
2、根据提示进行相应的操作,输入操作命令时仅输入一个字符,按回车表示结
束输入
3、在编码或是译码前要先建立哈弗曼树,否则报错
4、最后选择q或是Q结束程序
六、测试结果
1、建立哈弗曼树
2、编码:
3、译码
4、打印哈弗曼树
5、在已经建立哈弗曼树后再次选择i\或是I:
(1)若不需要重新建立哈弗曼树:
(2)若要重新建立哈弗曼树
6、如果在建树前进行编码、译码、打印哈弗曼树都会进行报错
7、选择q或是Q退出程序
七、附录
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<dos.h>
typedef struct
{
unsigned int weight;
unsigned int parent,lchild,rchild;
char elemt[20]; //定义哈弗曼树的类型
}HTNode,*HuffmanTree; //动态分配数组存储赫夫曼树
typedef char **HuffmanCode; //动态分配数组存储赫夫曼编码表
int Input() //判断输入的字符是否为正整数
{
char a[100];
int n=0,m,b=1,i;
scanf("%s",a); //先将输入的字符放入字符数组中
m=strlen(a);
for(i=m-1;i>=0;i--) //从最后一位开始判断是否在1到9之间
{
if('0'>a[i]||a[i]>'9'){printf("输入错误请重新输入\n");return -1;} //若不在1到9之间则输入不合法返回-1
else
{
n=n+(a[i]-48)*b; //如果合法则将输入的字符转换为十进制的整数
b=10*b;
}
}
return n; //返回输入合法的值
}
void Select(HuffmanTree &ht,int j,int &a0,int &a1) //查找数组中最小的两个数{
int i=0,d=0;
for(i=j;i>=0;i--)
{
if(ht[i].parent!=0) //在双亲不为0的条件下
continue;
if(d==0) //用a0记录第一个双亲不为0的元素所在的位置
{
a0=i;
d++;
}
else if(d==1) //用a1记录第一个双亲不为0的元素所在的位置
{
a1=i;
d++;
}
else if(d==2)
{
if(ht[a0].weight<=ht[a1].weight ) //a1用于存储最小值的位置
{
int h=a0;
a0=a1;
a1=h;
}
if(ht[i].weight<ht[a0].weight ) //a0用于存放次小值的位置
a0=i;
}
}
}
void HuffmanCoding(HuffmanTree &HT,HuffmanCode &HC,int &sum)
//哈弗曼树的建立
{
int n,m,i=0;
while(1) //输入合法时用break终止循环
{
printf("请输入字符的个数:\n");
if((n=Input())!=-1)break;
}
if(n<=1)return;
m=2*n-1;
sum=n;//n 个叶子的HuffmanTree共有2n-1个结点
HT=(HuffmanTree)malloc((m+1)*sizeof(HTNode));
printf("请输入字符: \n");
for(i=1;i<=n;++i) //给前n个单元初始化
{
scanf("%s",HT[i-1].elemt);
if(strlen(HT[i-1].elemt)!=1) //如果输入多个字符则输入错误
{
printf("输入错误,一次仅输入一个字符请重新输入:\n");
HT[i-1].elemt[0]='\0';
i=0; //输入有误时重新令i=1开始循环
continue;
}
for(int w=0;HT[w].elemt[0]!=HT[i-1].elemt[0];)
{w++;}
if(w<i-1){printf("输入重复:\n");i=0;continue;}
HT[i-1].parent=0; //双亲,左右孩子初始化
HT[i-1].lchild=0;
HT[i-1].rchild=0;
}
printf("请输入他们的权值:\n");
for(i=1;i<=n;i++)
{
if((HT[i-1].weight=Input())!=-1); //调用Input函数判断输入是否合法
else i=0; //当不合法时重新进行输入
}
for(;i<=m;++i) //从叶子之后的存储单元清零
{
HT[i-1].elemt[0]='\0';
HT[i-1].weight=0;
HT[i-1].parent=0;
HT[i-1].lchild=0;
HT[i-1].rchild=0;
}
for(i=n;i<=m-1;++i) //建Huffman树(从n个叶子后开始存内结点)
{
int s1=0,s2=0;
Select(HT,i-1,s1,s2);
//在HT[1…i-1]选择parent为0且weight最小的两个结点,其序号分别为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;
}
HC=(HuffmanCode)malloc((n+1)*sizeof(char *));
//分配n个字符编码的头指针向量
char *cd=(char *)malloc(n*sizeof(char)); //分配球编码的工作空间 cd[n-1]='\0'; //编码结束符
for (i=0;i<=n;++i ) //逐个字符求赫夫曼编码
{
int start=n-1; //编码结束符位置
int c,f=HT[i].parent;
for(c=i;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]); //从cd复制编码到HC
}
free(cd); //释放工作空间
printf("建表成功\n");
}
void Encoding(HuffmanTree HT,HuffmanCode HC,int sum)
//利用已经建立的哈弗曼树对输入的字符进行哈弗曼编码
{
char a[1000]; //用于存放输入的字符
char code[1000];
char *p=code; //用于存放字符对应的哈弗曼编码
printf("请输入要编码的字符:\n");
scanf("%s",a); //输入要编码的字符
for(int i=0;a[i]!='\0';i++) //依次判断字符的对应的哈弗曼编码
{
for(int n=0;HT[n].elemt[0];n++) //查找a[i]在哈弗曼树中的位置 {
if(a[i]==HT[n].elemt[0])
//如果找到就把对应的哈弗曼编码复制到字符数组code中
{
strcpy(p,HC[n]);p=p+strlen(HC[n]);break;
}
//把编码复制接到code后
}
if(n==sum&&a[i]!=HT[sum-1].elemt[0])
{
printf("输入中含有非法字符,无法进行编码\n");
return;
}
}
i=0;
printf("得到的编码是:\n");
while(code[i]!='\0') //输出字符对应的哈弗曼编码
{
if(i==50)printf("\n"); //当输出50个字符时,进行换行
printf("%c",code[i++]);
}
printf("\n");
}
void Decoding(HuffmanTree HT,HuffmanCode HC,int sum) //译码
{
char code1[1000]; //用于存放输入的密码
char a1[1000]; //用于存放翻译好的字符
int n=0,i=0,b=2*sum-2;
printf("请输入要编译的序列号\n");
scanf("%s",code1);
while(code1[i]!='\0')
{
if(code1[i]=='0') b=HT[b].lchild; //当遇到0时指向哈弗曼树的左子树 else if(code1[i]=='1') b=HT[b].rchild; //当遇到1时指向哈弗曼树的右子树 else //当不是0或1时输入的密码不合法,退出子函数
{
printf("输入的序列号错误\n");
return;
}
if(HT[b].lchild==0&&HT[b].rchild==0)
//当左右子树均为空时表明已找到对应的字符
{
a1[n++]=HT[b].elemt[0];b=2*sum-2;
//将对应的字符放在数组a1中,并重新设置b的值继续翻译
}
else
{
if(code1[i+1]=='\0')
//当结束时左右子树不都是空则输入的密码有误,退出子函数
{
printf("输入的序列号错误1\n");
return;
}
}
i++;
}
a1[n]='\0';
i=0;
while(a1[i]!='\0') //输出翻译好的字符
{
if(i==50) printf("\n");
printf("%c",a1[i++]);
}
printf("\n");
}
void Print(HuffmanTree HT,HuffmanCode HC,int sum) //打印哈弗曼树{
printf(" 地址元素双亲左孩子右孩子\n");
for(int i=0;i<2*sum-1;i++)
//从首元素开始,逐个输入哈弗曼树的各项数据
{
printf("%7d%9c%10d%9d%10d\n",i,HT[i].elemt[0],HT[i].parent,HT[i].lchild,HT[ i].rchild);
}
}
int main()
{
HuffmanTree HT0; //声明哈弗曼树
HuffmanCode HC0; //存放哈弗曼编码
int sum; //记录哈弗曼书中字符的总个数
int flag=0,flag1=1;
//flag1控制整个循环,当其为1时可以根据提示选择下需要进行的操作,当其为0时退出程序,flag用来表示哈弗曼树是否建立
char a[20],b[20];
while(flag1)
{
printf(" ------------------ 请按提示菜单选择需要的操作:-------------------\n");
printf(" I 建立哈弗曼表\n");
printf(" E 编码\n");
printf(" D 译码\n");
printf(" P 打印哈弗曼树\n");
printf(" Q 退出\n");
printf("-------------------------------------------------------------------\n");
printf("请输入要进行的操作:\n");
scanf("%s",a);
if(strlen(a)!=1) //当输入的操作命令超过一个字符时,输入错误
{
printf("输入错误,一次仅输入一个字母\0");//
a[1]='\0';
system("PAUSE"); //清屏
system("CLS");
printf("\n");
continue; //退出此次循环
}
if(a[0]=='I'||a[0]=='i') //进行建树操作
{
if(flag==1) //当flag为1时表示已建立哈弗曼树
{
printf("哈弗曼树已经初始化,确定重新建树?\n请选择YES(Y) or NO(N)\n");
while(flag)
{
scanf("%s",b); //选择重新建立哈弗曼树
if(strlen(b)!=1) //当输入的字符超过一个时输入错误
{
printf("输入错误,一次仅输入一个字母\0");
continue; //跳出此次循环,继续进行输入
}
if(b[0]!='Y'&&b[0]!='y'&&b[0]!='N'&&b[0]!='n')
//输入的字符不合法
{
printf("输入错误请重新输入\n");
continue;
//由于输入的字符不合法重新,跳出此次循环进行输入
}
if(b[0]=='n'||b[0]=='N') break;
//不需要重新建立哈弗曼树时,结束这次任务
else flag=0; //当需要进行重新建立哈弗曼树时,令flag为0 }
}
if(flag==0) //flag为0时建立哈弗曼树
{
HuffmanCoding(HT0,HC0,sum); //调用子函数,进行建立哈弗曼树操作flag=1; //已建好哈弗曼树
}
}
else if(a[0]=='e'||a[0]=='E') //进行编码
{
if(flag==0) //当flag为0时哈弗曼树未建立,结束此次操作
{
printf("还未建立哈弗曼编码\n");
}
else
Encoding(HT0,HC0,sum); //调用子函数对字符进行编码}
else if(a[0]=='d'||a[0]=='D') //进行译码
{
if(flag==0) //当flag为0时哈弗曼树未建立,结束此次操作
{
printf("还未建立哈弗曼编码\n");
}
else
Decoding(HT0,HC0,sum); //调用子函数,进行译码操作}
else if(a[0]=='p'||a[0]=='P') //打印哈弗曼树
{
if(flag==0) //当flag为0时哈弗曼树未建立,结束此次操作
{
printf("还未建立哈弗曼编码\n");
}
else
Print(HT0,HC0,sum); //调用子函数打印哈弗曼树
}
else if(a[0]=='Q'||a[0]=='q')flag1=0; //结束操作
else //当输入不合法时,重新选择要进行的操作printf("输入错误,请重新选择要进行的操作\n");
system("PAUSE");
system("CLS"); //清屏
}
return 0;
}
函数的调用关系:。