基于哈夫曼编码的数据压缩解压程序论文.docx
《基于哈夫曼编码的数据压缩解压程序论文.docx》由会员分享,可在线阅读,更多相关《基于哈夫曼编码的数据压缩解压程序论文.docx(28页珍藏版)》请在冰豆网上搜索。
基于哈夫曼编码的数据压缩解压程序论文
合肥学院
计算机科学与技术系
课程设计报告
2010~2011学年第二学期
课程
C++课程设计
课程设计名称
基于哈夫曼编码的数据压缩/解压程序
学生姓名
龚天棚
学号
1012091010
专业班级
网络工程
(1)班
指导教师
项响琴、徐静
2011年6月
一、需求分析
1.1课程设计目的
将理论教学中涉及到的知识点贯穿起来,对不同的数据类型、程序控制结构、数据结构作一比较和总结,结合设计题目进行综合性应用,对所学知识达到融会贯通的程度。
通过课程设计,学生在下述各方面的能力应该得到锻炼:
(1)进一步巩固、加深学生所学专业课程《C++程序设计语言》的基本理论知识,理论联系实际,进一步培养学生综合分析问题,解决问题的能力。
(2)全面考核学生所掌握的基本理论知识及其实际业务能力,从而达到提高学生素质的最终目的。
(3)利用所学知识,开发小型应用系统,掌握运用C++语言编写调试应用系统程序,训练独立开发应用系统,进行数据处理的综合能力。
(4)对于给定的设计题目,如何进行分析,理清思路,并给出相应的数学模型。
(5)掌握结构化程序设计方法,熟悉面向对象程序设计方法。
(6)熟练掌握C++语言的基本语法,灵活运用各种数据类型。
(7)进一步掌握在集成环境下如何调试程序和修改程序。
1.2课程设计名称及内容
课程设计名称:
基于哈夫曼编码的数据压缩/解压程序
设计内容:
将任意一个指定的文本文件中的字符进行哈夫曼编码,生成一个编码文件(压缩文件);反过来,可将一个压缩文件解码还原为一个文本文件。
1.3任务和要求
1)可设计一个菜单:
Q----Quit
L----ListTextDocument
D----Decoding
C----Coding
2)选择C时:
输入一个待压缩的文本文件名称(可带路径)。
如:
D:
\lu\lu.txt
统计文本文件中各字符的个数作为权值,生成哈夫曼树;
将文本文件利用哈夫曼树进行编码,生成压缩文件。
压缩文件名称=文本文件名.COD如:
D:
\lu\lu.COD
压缩文件内容=哈夫曼树的核心内容+编码序列
3)选择D时:
输入一个待解压的压缩文件名称(可带路径)
如:
D:
\lu\lu.COD
从文件中读出哈夫曼树,并利用哈夫曼树将编码序列解码;
生成(还原)文本文件。
文件文件名称=压缩文件名+"_new.txt"
如:
D:
\lu\lu_new.txt
4)选择L时:
输入一个待压缩的文本文件名称(可带路径)。
如:
D:
\lu\lu_new.txt
显示出该文本文件的内容
5)功能扩展(自己定制):
编码使用二进制位,利用位运算进行真正的数据压缩。
可对任何文件进行压缩。
显示出各种重要信息,如压缩率,各字符的哈夫曼编码表。
二、算法设计
2.1设计思想:
2.2算法思想:
2.2.1输入要压缩的文件
首先运行的时候,用户主界面上有菜单提示该如何使用软件,根据菜单提示选择所要执行的项,依次进行,因为各个环节之间有先后顺序。
第一步为输入压缩软件的名称,由键盘输入文件路径和文件名称,读入字符数组中,打开该文件,按照提示进行压缩。
若打不开,则继续输入。
2.2.2读文件并计算字符频率
文件将信息存放在字符数组中;计算每个字符出现的次数,申请一个结构体数组空间,用读取的字符减去字符结束符作为下标记录字符的频率。
2.2.3根据字符的频率,利用Huffman编码思想创建Huffman树
将所记录的字符的频率作为权值来创建Huffman树,依次选择权值最小的两个字符作为左右孩子,其和作为父结点的权值,依次进行下去,直到所有的字符结点都成为叶子结点。
2.2.4由创建的Huffman树来决定字符对应的编码,进行文件的压缩
根据创建的Huffman树来确定个字符的01编码,左孩子为0,右孩子为1。
读取文件,依次将每个字符用他们的编码表示,即完成一次编码。
2.2.5解码压缩即根据Huffman树进行译码
读取编码文件,依据创建的Huffman树,定义一个指针指向根结点。
从根结点开始,每读一个字符,指针变化一次(当读取的字符是‘1’时,指针指向当前所指结点的右孩子,当读取的字符是‘0’时,指针指向当前所指结点的左孩子),直至该指针所指结点为叶子结点时结束(即当结点的左右孩子均为空时)。
将当前叶子结点所代表的字符值输出到译码文件中,依次读取编码文件中的字符,按照上述方法依次进行下去直至文件
2.3主要模块说明
下面是该系统的模块
首先定义一个结构体:
structhead
{
unsignedcharb;//记录字符
longcount;//权重
intparent,lch,rch;//定义双亲,左孩子,右孩子
charbits[256];//存放哈夫曼编码的数组
}
header[512],tmp;//头部一要定设置至少512个,因为结点最多可达256,所有结点数最多可达511
然后是实现各个功能的函数:
voidshow() 显示文本文件的内容
unsignedcharctoa(chara[])将数组的前八位转成二进制形式bit位
char*code(unsignedchartemp,intleafnum)寻找对应字符的编码串,并返回
voidcompress(char*infilename,char*outfilename)对文本编码函数
voiduncompress(char*infiname,char*outfilename)对压缩文件解码
voidctoa(unsignedchara,charcode[])字符转为二进制形式存入8位数组
intstrcmp1(charbuf[],structheadhead[],intn,unsignedchar&c)
将buf字符串与header[i].bits[]中匹配,成功后对应的字符由c带回
voidstrcpy1(charbuf[],chara[],int)
将字符串a中长度为j的部分复制到buf数组中
最后是主函数:
在主函数中含有菜单函数voidMainMenu()和帮助函数voidhelp(),最后通过switch语句,调用各种函数,分别完成各自的功能。
2.4部分重要函数的实现:
voidcompress(char*infilename,char*outfilename)的实现:
1)记录文件中字符频度;
2)根据频度建树;
3)根据哈夫曼树编码;
4)对文件进行编码,写入新文件(核心);
5)将字符编码对照表写入文件;
6)将文件的哈夫曼编码输出到显示器上。
voidcompress(char*infilename,char*outfilename)的实现:
1)读入必要的数据;
2)读入编码对照表,放入header[i].bits[]数组中;
3)对读入的编码对照表进行排序,长度短的排在前面;
4)将编码读入内容,进行解码工作。
三、用户手册
运行后的主界面会提示用户进行想要的操作。
1)编码(压缩文件)操作。
若用户想要对某一文件进行压缩,则按主界面的提示按“C”,然后界面提示输入进行压缩操作的文件路径和文件名,输入完成后按回车键,此时界面会提示输入压缩后文件的保存路径及其文件名,输入完成后再按回车键,便可完成编码,同时会显示出各字符的哈夫曼编码,系统也会提示用户压缩文件过程结束。
2)译码(还原文件)操作
若用户想对某一压缩文件进行解压操作,则按主界面的提示按“D”,然后界面提示输入进行解压操作的文件路径和文件名,完成输入后按回车键,此时界面会提示输入解压后的文件的保存路径及其文件名,输入完成后再按回车键,便可完成译码,系统提示用户还原文件过程结束。
3)显示数据内容
若用户想知道文本输入的内容,可输入“L”,然后界面提示输入文本文件的路径和文件名,完成输入后按回车键,界面会出现文本的内容。
4)帮助操作
若用户不知道怎么使用该软件,则按主界面的提示按“H”,然后界面会出现教您怎么使用该软件的界面。
5)操作失败后的界面提示,若用户输入的文件路径和文件名错误,则界面会提示用户输出文件打开失败。
用户根据需求,在操作过程中按“Q”可随时退出系统。
四、测试结果
4.1压缩过程:
在D盘中建立一个文本文档,并命名为123.txt。
通过程序编译:
在D盘中生成一个.COD的文档,并且名为123_new.COD:
4.2解压过程
解压过程就是将刚才压缩的文本文件还原。
通过程序编译:
在D盘生成一个.txt的文件,并且名为456.txt,同时文本的内容与原来的文本信息相同,实现解压功能。
4.3显示文本内容
将文本的内容输出在显示器上
通过程序编译过后:
4.4显示帮助界面:
五、总结
在当今信息时代,如何采用有效的数据压缩技术来节省数据文件的存储空间和计算机网络的传送时间已越来越引起人们的重视,哈夫曼编码正是一种应用广泛且非常有效的数据压缩技术。
在课程设计过程中,我选择了《基于哈夫曼编码的数据压缩与解压程序》这一课题,虽然这个课题所涉及的知识我们还没有学习到,属于数据结构与算法的内容,但通过借助书本,自己动手实践,还是掌握了一点关于数据结构的知识,通过一周的课程设计使我对哈夫曼树以及哈夫曼编码有了更深的认识和理解,利用哈夫曼编码的思想方法,熟练掌握哈夫曼编码的过程。
创建二叉树的方法和二叉树的存储结构,知道压缩文件是如何进行的,解压缩即为它的逆过程。
程序的模块化结构尤其重要,应掌握各个模块间的逻辑关系和整体程序的结构。
其实在这次课程设计中遇到很多问题,第一就是知识没有学到,必须从最基本的书本知识看起,谢谢指导老师借给我的那本数据结构与算法的课本,对我帮助很大,使我对程序的一些算法有了最基本的了解。
其次通过网络资源使我了解的知识更加丰富,增强了我对网络资源的检索能力,使我能够更好的应用网络资源来完成自己需要完成的任务。
所以课程设计不仅能培养我们的专业知识,而且还能培养我们的动手能力,使我们以后能够更加适应社会的发展。
通过这次课程设计,使我的自学能力有所提高,让我知道了怎么去接受一个新的知识并且能够很好的掌握它。
同时也增强我的独立思考能力和动手能力;通过编写程序代码和调试运行,我们可以逐步积累调试程序的经验,逐渐培养我们的编程能力和利用计算机解决实际问题的能力。
课程设计为我们提供了一个自己动手实践的平台。
另一方面,在课程设计的过程中,使我明白了面向对象与面向对象的差别。
在面向对象过程中,类的设计是至关重要的,类设计好了等于程序就成功了一半,所以这次的课程设计帮助我复习了这一学期面向对象课程的学习,刚好可以弥补这一学期面向对象学习的不足。
同时,也使我对数据结构与算法的知识有了一定的了解,帮我在大二学习数据结构与算法的课程中奠定了一定的基础,使我以后学习数据结构与算法的时候可以更加轻松。
这次课程设计不但使我掌握了一些知识,更重要的是使我认识到了作为程序员的艰辛和辛苦。
一个星期面对着电脑,在你面前只有一行行的代码,在你的耳旁只有键盘的敲击声和点击鼠标的声音,说实话确实很无聊也很辛苦。
但当自己看到自己编写的程序顺利通过的时候,心里的成就感就油然而生,心中的疲倦也消失了,我想,当程序员看到自己的程序能够编译成功的话,也是这种感觉吧!
虽然这次课程设计结束了,但我们学习C++等语言的步伐不能停止,在今后的学习过程中,我会更加努力,争取在今后的课程中学得更好,下次的课程设计能够更加成功。
六、参考资料
[1].郑莉等编著《C++语言程序设计(第三版)》北京:
清华大学出版社
[2].郑莉等编著《C++语言程序设计(第三版)学生用书》北京:
清华大学出版社
[3].李春葆等编著《C++程序设计学习与上机实验指导》北京:
清华大学出版社
[4].范辉等编著《VisualC++6.0程序设计简明教程》高等教育出版社
[5].李龙澍《C++程序设计实训教程》北京:
清华大学出版社
[6].洪国胜等编著《C++Builder程序设计轻松上手》北京:
清华大学出版社
[7].严蔚敏等《数据结构(c语言版)》北京:
清华大学出版社,1997年4月第1版
[8].胡学钢等《数据结构算法设计指导》北京:
清华大学出版社,1999年第1版
[9].郑莉等编著《C++语言程序设计(第4版)学生用书》北京:
清华大学出版社
[10].苏小红等编著《C语言大学实用教程》北京:
电子工业出版社
[11].王昆仑等编著《数据结构域算法(高等学校计算机精品课程系列教材)》中国铁道工业出版社
七、附录
7.1源代码
#include
#include
#include
#include
#include
usingnamespacestd;
structhead
{
unsignedcharb;//记录字符
longcount;//权重
intparent,lch,rch;//定义双亲,左孩子,右孩子
charbits[256];//存放哈夫曼编码的数组
}
header[512],tmp;//头部一要定设置至少512个,因为结点最多可达256,所有结点数最多可达511
unsignedcharctoa(chara[])/*将数组的前八位转成二进制形式比特位*/
{
unsignedcharc=0;
for(inti=0;i<8;i++)
if(a[i]!
=0)
{
c=c+(int)(a[i]-'0')*pow(2,8-1-i);
}
returnc;
}
char*code(unsignedchartemp,intleafnum)//寻找对应字符的编码串,并返回
{
for(inti=0;iif(temp==header[i].b)
returnheader[i].bits;
returnNULL;
}
voidcompress(char*infilename,char*outfilename)
{
longflength=0;//记录压缩前文件长度
longclength=8;//编码从偏移量8记录,统计压缩后编码长度加8
intleafnum;//定义叶子结点
intpointnum;//定义总结点
unsignedchartemp;//定义unsignedchar类型,暂存文件中字符的中间变量
/*********************************文件中字符频度************************************/
for(inti=0;i<256;i++)
{
header[i].count=0;//初始化权重
header[i].b=(unsignedchar)i;//初始化字符
}
ifstreaminfile(infilename,ios:
:
in|ios:
:
binary);
while(infile.peek()!
=EOF)
{
infile.read((char*)&temp,sizeof(unsignedchar));//读入一个字符
header[temp].count++;//统计对应结点字符权重
flength++;//统计文件长度
}
infile.close();//关闭文件
for(i=0;i<256-1;i++)//对结点进行冒泡排序,权重大的放在上面,编码时效率高
for(intj=0;j<256-1-i;j++)
if(header[j].count{
tmp=header[j];
header[j]=header[j+1];
header[j+1]=tmp;
}
for(i=0;i<256;i++)
if(header[i].count==0)break;
leafnum=i;//取得哈夫曼树中叶子结点数
pointnum=2*leafnum-1;//取得哈夫曼树中总结点数目
/**********************************根据频度建树*************************************/
longmin;//尽量用long,如果文件过大,这里建树可能不是最优树了
ints1,s2;
for(i=leafnum;i{
min=999999999;
for(intj=0;j
if(header[j].parent==0&&header[j].count{
min=header[j].count;
s1=j;
}
header[s1].parent=i;//填写第一个叶子结点信息
min=999999999;
for(j=0;j
if(header[j].parent==0&&header[j].count{
min=header[j].count;
s2=j;
}
header[s2].parent=i;
header[i].count=header[s1].count+header[s2].count;//填写父结点信息
header[i].lch=s1;
header[i].rch=s2;
}
/*********************************根据哈夫曼树编码**********************************/
chartmp[256];//定义临时变量,暂存编码
tmp[255]='\0';//添加结束标志
intstart;
intc;//记录当前叶结点下标
intf;//存储父结点的下标
for(i=0;i{
start=255;//另开始等于数组最后位
for(c=i,f=header[i].parent;f!
=0;c=f,f=header[f].parent)//对叶结点进行编码
if(header[f].lch==c)tmp[--start]='0';
elsetmp[--start]='1';
strcpy(header[i].bits,&tmp[start]);
}
/************************************对文件进行编码,写入新文件(核心)*********************************/
infile.open(infilename,ios:
:
in|ios:
:
binary);//打开待压缩的文件
infile.clear();
infile.seekg(0);
ofstreamoutfile(outfilename,ios:
:
out|ios:
:
binary);//打开压缩后将生成的文件
outfile.write((char*)&flength,sizeof(long));//写入原文件长度
charbuf[513]="\0";//初始化编码缓冲区
outfile.seekp(8);//指针定向偏移量8
while(infile.peek()!
=EOF)
{
infile.read((char*)&temp,sizeof(unsignedchar));//读入字符
strcat(buf,code(temp,leafnum));//检索出字符对应编码,连到buf[]中
while(strlen(buf)>=8)//当buf中字符长度大于8时,一直处理写入,直至小于8
{
temp=ctoa(buf);//上面临时变量已经完成使命,可以赋新值了
outfile.write((char*)&temp,sizeof(unsignedchar));//转成二进制写入
clength++;//统计代码结尾偏移加1,用于找到叶子结点位置
strcpy(buf,buf+8);//字符串前移八位
}//当此循环结束时,表示buf[]中已经小于8了,没到文件末尾,读下一个,继续,否则退出
}//while此层循环退出时,表示已到末尾,再判断buf中是否写完,没写完,连满至少8个字符,再写一个字节,就够了
if(strlen(buf)>0)
{
strcat(buf,"0000000");
temp=ctoa(buf);//前八位转成二进制形式
outfile.write((char*)&temp,sizeof(unsignedchar));
clength++;//统计代码结尾偏移加1,用于找到叶子结点位置
}
outfile.seekp(4);
outfile.write((char*)&clength,sizeof(long));//写入文件中将记录叶子结点位置
infile.close();
/*************************************将字符编码对照表写入文件****************************************/
longbytelen;//记录编码以二进制存储时需要占多少个字节
outfile.clear();
outfile.seekp(clength);//将文件指针移到编码后面的第一位置,在此处记录叶子结点数
outfile.write((char*)&leafnum,sizeof(long));//写入叶子结点数
for(i=0;i{
outfile.write((char*)&header[i].b,sizeof(unsignedchar));//写入字符
header[i].count=strlen(header[i].bits);//不再设置其他变量,权值这时已无使用价值,可以用相应结点的权值变量记录长度
outfile.write((char*)&header[i].count,sizeof(unsignedchar));//写入长度的ASCII码
if(header[i].count%8==0)
bytelen=header[i].count/8;
else
{
bytelen=header[i].count/8+1;
strcat(header[i].bits,"0000000");//在编码后面补0,使其最后凑满8的倍数,
//超过无妨,可以用bytelen控制好写入字节的长度
}
for(i