哈夫曼编译码器设计说明书
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
*******************
实践教学
*******************
兰州理工大学
计算机与通信学院
2012年春季学期
数据结构课程设计
题目: 哈夫曼编译码器设计
专业班级:信息与计算科学
姓名:丁朋
学号:10500112
指导教师:张永
成绩:_______________
目录
摘要 (1)
1.采用类C语言定义相关的数据类型 (2)
2.各模块的伪码算法 (2)
3.函数的调用关系图 (2)
4.调试分析 (9)
5.测试结果 (10)
6.源程序(带注释) (14)
总结 (23)
参考文献 (24)
致谢 (25)
摘要
本次课程设计主要根据<<数据结构>>中所学的哈夫曼树知识进行拓展,实
现发送端对传送数据的编码和接收端对传送来的数据的译码。
哈夫曼编译码算
法主要有八个功能模块构成:初始化(建立哈夫曼树)函数、编码函数、译码函数、
打印代码函数、打印哈夫曼树函数、哈夫曼编码表:、操作界面函数和主函数。
我主要根据课本中的实现思想及算法编写程序,体现以课本知识的应用为主,在学习了线性表、栈、队列、二叉树、树和图等结构的基础上,以能够更加熟练的应用所学知识,并能够结合一些著名算法来实现对一些实际问题的应用,例如,这次的哈夫曼编译码系统的实现,从而进一步的理解数据结构这本书的诸多所学知识,并熟练掌握一些算法及其运用,虽然有些知识平时学得不是很透彻,但通过这次的课程设计,当遇到不是很懂的问题时,通过思考、查资料或向老师求教,最终都迎刃而解,加深了对所学知识的掌握程度。
关键词:构造哈弗曼树;实现编码;实现译码;
1.采用类c语言定义相关的数据类型
typedet struct {
char ch; // 存放编码的字符
char bits[N+1]; // 存放编码位串
int len; // 编码的长度
}CodeNode; // 编码结构体类型
typedet struct {
char ch; // 存放编码的字符
char bits[N+1]; // 存放编码位串
int len; // 编码的长度
}CodeNode; // 编码结构体类型
2.各模块的伪码算法
(1)哈弗曼树的算法
void CreateHT(HTNode ht[],int n) //调用输入的数组ht[],和节点数n
{
int i,k,lnode,rnode;
int min1,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<min1) //若权值小于最小的左节点的权值
{
min2=min1;rnode=lnode;
min1=ht[k].weight;lnode=k;
}
else if (ht[k].weight<min2)
{
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)哈弗曼编码
void CreateHCode(HTNode ht[],HCode hcd[],int n)
{
int i,f,c;
HCode hc;
for (i=0;i<n;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;
}
}
void DispHCode(HTNode ht[],HCode hcd[],int n) //输出哈夫曼编码的列表{
int i,k;
printf(" 输出哈夫曼编码:\n");
for (i=0;i<n;i++) //输出data中的所有数据,即A-Z
{
printf(" %c:\t",ht[i].data);
for (k=hcd[i].start;k<=n;k++) //输出所有data中数据的
编码
{
printf("%c",hcd[i].cd[k]);
}
printf("\n");
}
}
void editHCode(HTNode ht[],HCode hcd[],int n) //编码函数
{
char string[MAXSIZE];
int i,j,k;
scanf("%s",string); //把要进行编码的字符串存入string数组中
printf("\n输出编码结果:\n");
for (i=0;string[i]!='#';i++) //#为终止标志
{
for (j=0;j<n;j++)
{
if(string[i]==ht[j].data) //循环查找与输入字符相同的编号,相同的就输出这个字符的编码
{
for (k=hcd[j].start;k<=n;k++)
{
printf("%c",hcd[j].cd[k]);
}
break; //输出完成后跳出当前for循环}
}
}
(3)哈弗曼译码
void deHCode(HTNode ht[],HCode hcd[],int n) //译码函数
{
char code[MAXSIZE];
int i,j,l,k,m,x;
scanf("%s",code); //把要进行译码的字符串存入code数组中
while(code[0]!='#')
for (i=0;i<n;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];
}
}
}
3.函数调用关系图
开始
对各结点赋初值,并令i=0
N
i<weightNum-1
Y
指出无双亲结点中权值最小和次
小的结点S1,S2
两个结点合并成一个新的无双亲结点
i++
结束
开始
第一个字符,i=0
I<LeafNum
结点是根节点?
父节点的左子节点==该节点
记编码“0”记编码“1”
i++
结束
将得到的编码存储
N
Y
Y
N
Y N
4.调试分析
a、调试中遇到的问题及对问题的解决方法
本次程序的运行结果与预期结果最终达到了一致。
在运行阶段,虽然每次都能得到预期结果,但是操作结果界面有时不是很完美,于是通过修改程序代码(只是略加修改,并没有作大篇幅的改动),不断执行程序,进行完善,直到得出满意的操作界面为止。
b、算法的时间复杂度和空间复杂度
算法的时间复杂度为O(n2)
算法的空间复杂度为O(n)
5.测试结果
6.源程序(带注释)
#include<iostream>
#include<fstream>
#include<string>
using namespace std;
typedef struct HuffmanNode
{//结点结构
int weight;
int parent,lchild,rchild;
}*HfmNode;
struct HuffmanTree
{//哈弗曼树
HfmNode Node;
char *Info;//存储字符,也可放在结点结构里定义
int LeafNum;//叶结点数量
};
HuffmanTree T;//连接各模块变量
/****************初始化(建立哈夫曼树)函数********************/ void Initialization() //初始化
{
int WeightNum;
int i,j,pos1,pos2,max1,max2; //
cout<<"输入字符个数:";
cin>>WeightNum;
T.Node=new HuffmanNode[2*WeightNum-1];
=new char[2*WeightNum-1];
for(i=0;i<WeightNum;i++)
{
cout<<"请输入第"<<i+1<<"个字符值";
cin.get (); //输入字符
[i]=cin.get ();
cout<<"请输入该字符的权值或频度";
cin>>T.Node[i].weight; //输入权值
T.Node[i].parent=-1; //为根结点
T.Node[i].lchild=-1; //无左孩子
T.Node[i].rchild=-1; //无右孩子
}
/*************************************************************************/ for(i=WeightNum;i<2*WeightNum-1;i++) //建立哈夫曼树
{
pos1=-1;
pos2=-1; //分别用来存放当前最小值和次小值的所在单元编号
max1=32767; //32767为整型数的最大值
max2=32767; //分别用来存放当前找到的最小值和次小值
for(j=0;j<i;j++) //在根节点中选出权值最小的两个
{
if(T.Node[j].parent==-1) //是否为根结点
{
if(T.Node[j].weight<max1)
{
max2=max1;
max1=T.Node[j].weight;
pos2=pos1; //修改次小值所在单元编号
pos1=j; //修改最小值所在单元编号
}
else
{
if(T.Node[j].weight<max2) //比原最小值大但比原次小值要小
{
max2=T.Node[j].weight; //存放次小值
pos2=j; //修改次小值所在的单元编号
}
}
}
}
T.Node[pos1].parent=i; //修改根节点位置
T.Node[pos2].parent=i;
T.Node[i].lchild=pos1; //修改儿子节点位置
T.Node[i].rchild=pos2;
T.Node[i].parent=-1; //表示新结点应该是根结点
T.Node[i].weight=T.Node[pos1].weight+T.Node[pos2].weight;
}
T.LeafNum=WeightNum;
ofstream outfile("hfmTree.dat");
if(!outfile)
{
cout<<"打开文件失败!"<<endl;
return;
}
outfile.write((char*)&WeightNum,sizeof(WeightNum)); //写入字符个数
for(i=0;i<WeightNum;i++) //把各字符信息写入文件outfile.write((char*)&[i],sizeof([i]));
for(i=0;i<2*WeightNum-1;i++) //把个节点内容写入文件outfile.write((char*)&T.Node[i],sizeof(T.Node[i]));
outfile.close();
cout<<"已建立哈夫曼树!"<<endl;
}
/****************编码函数********************/
void Encoding()
{
if(T.Node==NULL) //哈夫曼树不在内存,从文件hfmTree中读入
{
ifstream infile0; //以二进制方式打开hfmTree.dat文件
infile0.open("hfmTree.dat",ios::binary|ios::in);
if(infile0.fail())
{
cout<<"文件打开失败!\n";
return;
}
infile0.read((char*)&T.LeafNum,sizeof(T.LeafNum)); //读取叶子结点数
=new char[T.LeafNum];//为叶子结点分配大小为LeafNum的存储空间
T.Node=new HuffmanNode[2*T.LeafNum-1];
for(int i=0;i<T.LeafNum;i++) //读取字符信息
infile0.read((char*)&[i],sizeof([i])); for(i=0;i<2*T.LeafNum-1;i++) //读取结点信息
infile0.read((char*)&T.Node[i],sizeof(T.Node[i])); infile0.close(); //关闭哈夫曼树
}
char *Tree; //用于存储需编码内容
int i=0,k=0;
string ch;
cout<<"请输入测试数据(输入完毕按两次回车):"<<endl;
cin.ignore();
getline(cin,ch,'\n'); //回车键作为输入结束条件。
while(ch[k]!='\0')//统计输入字符个数
k++;
Tree=new char[k+1]; //为输入的字符动态分配大小为k+1的存储空间
k=0;
while(ch[k]!='\0')//将输入的内容存到Tree中
{
Tree[k]=ch[k];
k++;
}
Tree[k]='\0';
cout<<"需编码内容为:";
cout<<Tree<<endl;
ofstream outfile("CodeFile.txt"); //存储编码后的代码,并覆盖原文件
if(T.Node==NULL) //还未建哈夫曼树
{
cout<<"警告+提示:请先建树!\n";
return;
}
char *code;
code=new char[T.LeafNum]; //为所产生编码分配容量为T.LeafNum的存储空间k=0;
while(Tree[k]!='\0')
{
int j,start=0;
for(i=0;i<T.LeafNum;i++)
if([i]==Tree[k]) //求出该文字所在单元的编号
break;
j=i;
while(T.Node[j].parent!=-1) //结点j非树根
{
j=T.Node[j].parent; //非结点j的双亲结点
if(T.Node[j].lchild==i) //是左子树,则生成代码0
code[start++]='0';
else //是右子树,则生成代码1
code[start++]='1';
i=j;
}
int m=start-1;
while(m>=0) //存储哈弗曼编码
{
outfile<<code[m];
m--;
}
k++;
}
outfile.close();
cout<<"已编码!且编码形式内容已存到文件CodeFile.txt中!\n\n";
delete Tree;
delete code; //释放动态分配的空间
} //Encoding
/****************译码函数********************/
void Decoding(){
int i=0,k=0;
int j=T.LeafNum*2-1-1; //从根结点开始往下搜索
char* str;
char ch;
ifstream infile1("CodeFile.txt");
if(!infile1)
{
cout<<"请先编码!\n";
return;
}
cout<<"经译码,原内容为:";
while(infile1.get(ch))
{
k++; //计算CodeFile中代码长度
}
infile1.close();
str=new char[k+1];
ifstream infile2("CodeFile.txt");
k=0;
while(infile2.get(ch))
{
str[k]=ch; //读取文件内容
k++;
}
infile2.close();
str[k]='\0'; //结束标志符
if(T.Node==NULL) //还未建哈夫曼树
{
cout<<"请先编码!\n";
return;
ofstream outfile("TextFile.txt"); //将字符形式的编码文件写入文件Textfile中while(str[i]!='\0')
{
if(str[i]=='0')
j=T.Node[j].lchild; //往左走
else
j=T.Node[j].rchild; //往右走
if(T.Node[j].rchild==-1) //到达叶子结点
{
cout<<[j]; //输出叶子结点对应的字符
outfile.put([j]);
j=T.LeafNum*2-1-1; //存入文件
}
i++;
}
outfile.close();
delete str;
cout<<"\n译码成功且其内容已存到文件TextFile.txt中!\n\n";
}//Decoding
/*********************打印代码函数***************************/
void Print1(){
char ch;
ifstream infile("Codefile.txt");//用文件输入流ifstream从文件"Codefile.txt"中读取数据if(!infile)
{
cout<<"未进行编码"<<endl;
return;
}
ofstream outfile("CodePrin.txt");//写入到磁盘的"CodePrin.txt"中,打开文件,用于输出if(!outfile)
{
cout<<"打开失败!"<<endl;
return;
}
int i=0;
int j=T.LeafNum*2-1-1; //从根结点开始往下搜索
while(infile.get(ch)) // 扫描文件中的每个字符
{
cout<<ch;
i++;
if(i==50)
{i=0;
cout<<endl;
if(ch=='0')
j=T.Node[j].lchild; //往左走
else
j=T.Node[j].rchild; //往右走
if(T.Node[j].rchild==-1) //到达叶子结点
{
outfile.put([j]); //输出叶子结点对应的字符存入文件
j=T.LeafNum*2-1-1;
}
}
cout<<endl;
infile.close();
outfile.close();
cout<<"相应的字符形式的编码文件已写入CodePrin.txt中!"<<endl;
}
/****************打印哈夫曼树函数********************/
void Tree_Printing()
{
if(T.Node==NULL) //未建立哈夫曼树
{
cout<<"请先建立哈夫曼树!\n";
return;
}
cout<<"所建立的哈夫曼树的凹入表形式为:"<<endl;
ofstream fop("TreePrint.txt"); //将所建立的哈夫曼树写入文本文件TreePrint
cout<<"位置:权值(字符) "<<"左孩子位置(权值) "<<"右孩子位置(权值)\n";//dos窗口中显示fop<<"位置:权值(字符) "<<"左孩子位置(权值) "<<"右孩子位置(权值)\n";//在文档Treeprint 中显示
int i;
for(i=0;i<T.LeafNum;i++)
{
cout<<i<<":"<<T.Node[i].weight<<"("<<[i]<<") \n";//依照建树时输入的次序依次输出i 个节点的权值和对应的字符
fop<<i<<":"<<T.Node[i].weight<<"("<<[i]<<") \n";
}
for(i=T.LeafNum;i<(2*T.LeafNum-1);i++)
{
cout<<i<<":"<<T.Node[i].weight<<"(#)"<<"-------"
<<T.Node[i].lchild<<"("<<T.Node[T.Node[i].lchild].weight<<")"<<"----"
<<T.Node[i].rchild<<"("<<T.Node[T.Node[i].rchild].weight<<")"<<endl;
fop<<i<<":"<<T.Node[i].weight<<"(#)"<<"------"
<<T.Node[i].lchild<<"("<<T.Node[T.Node[i].lchild].weight<<")"<<"-----"
<<T.Node[i].rchild<<"("<<T.Node[T.Node[i].rchild].weight<<")"<<endl;
}
/****************哈夫曼编码表:********************/
void Print(){
cout<<"哈夫曼编码表:"<<endl;
char *code;
code=new char[T.LeafNum]; //为产生编码动态分配存储空间
int i=0;
while(i<T.LeafNum) //求出该文字所在单元的编号
{
cout<< [i]<<":";
int j,start=0;
int k;
k=i;
j=k;
while(T.Node[j].parent!=-1) //结点j非树根
{
j=T.Node[j].parent; //非结点j的双亲结点
if(T.Node[j].lchild==k) //是左子树,则生成代码0
code[start++]='0';
else //是右子树,则生成代码1
code[start++]='1';
k=j;
}
for(int n=start-1;n>=0;n--) //输出哈夫曼编码
cout<<code[n];
i++;
cout<<endl;
}
delete code; //释放动态分配的存储空间
}
/***************************操作界面函数**********************************/ void Screen_display()
{
char ch;
do{
cout<<"**********************************************************"<<endl;
cout<<" 哈夫曼编码/译码系统"<<endl;
cout<<endl;
cout<<" 操作指令目录"<<endl;
cout<<endl;
cout<<" I:初始化(建立哈夫曼树) E:编码D:译码P:打印代码T:打印哈夫曼树Q:退出系统"<<endl;
cout<<endl;
cout<<"版本:@ DOTA6.60A"<<endl;
cout<<"**********************************************************"<<endl;
cout<<endl;
cout<<"输入相应操作的指令(不分大小写):"<<endl;
cin>>ch;
switch(ch)
{
case'I':
case'i':cout<<" 现在进行'初始化'操作:"<<endl;Initialization();break;
case'E':
case'e':cout<<" 现在进行'编码'操作: "<<endl;Encoding();break;
case'D':
case'd':cout<<" 现在进行'译码'操作: "<<endl;Decoding();break;
case'P':
case'p':cout<<" 现在进行'打印代码'操作: "<<endl;Print1();break;
case't':
case'T':{Tree_Printing();cout<<endl;Print();break;}
case'Q':
case'q':cout<<"谢谢使用!"<<endl;exit(1);break;
}
}while(1);
}
/****************主函数********************/
void main()
{
Screen_display();
}
总结
在我自己课程设计中,就在编写好源代码后的调试中出现了不少的错误,遇到了很多麻烦及困难,我的调试及其中的错误和我最终找出错误,修改为正确的能够执行的程序中,通过分析,我学到了:
在定义头文件时可多不可少,即我们可多写些头文件,肯定不会出错,但是若没有定义所引用的相关头文件,必定调试不通过;
在执行译码操作时,不知什么原因,总是不能把要编译的二进制数与编译成的字符用连接号连接起来,而是按顺序直接放在一起,视觉效果不是很好。
还有就是,很遗憾的是,我们的哈夫曼编码/译码器没有像老师要求的那样完成编一个文件的功能,这是我们设计的失败之处。
通过本次数据结构的课程设计,我学习了很多在上课没懂的知识,并对求哈夫曼树及哈夫曼编码/译码的算法有了更加深刻的了解,更巩固了课堂中学习有关于哈夫曼编码的知识,真正学会一种算法了。
当求解一个算法时,不是拿到问题就不加思索地做,而是首先要先对它有个大概的了解,接着再详细地分析每一步怎么做,无论自己以前是否有处理过相似的问题,只要按照以上的步骤,必定会顺利地做出来。
这次课程设计,我在编辑中犯了不应有的错误,设计统计字符和合并时忘记应该怎样保存数据,对文件的操作也很生疏。
在不断分析后明确并改正了错误和疏漏,我的程序有了更高的质量。
参考文献
.
1. 严蔚敏,吴伟民.《数据结构(C语言版)》.清华大学出版社.
2. 严蔚敏,吴伟民.《数据结构题集(C语言版)》.清华大学出版社.
3.《DATA STRUCTURE WITH C++》. William Ford,William Topp .清华大学出版社(影印版).
4 .谭浩强.《c语言程序设计》. 清华大学出版社.
5.数据结构与算法分析(Java版), A Practical Introduction to Data Structures and Algorithm Analysis Java Edition , 张铭,刘晓丹译电子工业出版社2001 年1月
致谢
我很荣幸能够进行这个专业的学习,因此才有了做这个课程设计的机会,因为是在时间紧迫并且伴随着考试的压力下完成的,也是第二次接触课程设计,并能够顺利完成,感觉挺有成就感的,在这个过程中,我学到的远远不只是做这个课程设计,有了其他三位同学作为一组,给我的最深刻感受是:有她们真好,我领悟到了合作中团结互助精神的重要性!这次课程设计报告的成功完成将会为下次的其他课程设计和毕业设计等打下良好的基础!最后,诚挚感谢老师的指导和监督!。