哈夫曼编码译码器课程设计.docx
《哈夫曼编码译码器课程设计.docx》由会员分享,可在线阅读,更多相关《哈夫曼编码译码器课程设计.docx(22页珍藏版)》请在冰豆网上搜索。
哈夫曼编码译码器课程设计
课程设计说明书
课程名称:
数据结构与算法
设计题目:
哈夫曼编\译码器
院系:
计算机科学与信息工程学院
学生姓名:
刘文杰
学号:
16031210229
专业班级:
软件工程16-2
指导教师:
孙高飞
2017年12月11日
设计题目
哈夫曼编\译码器
限定人数
3
问题描述
采用哈夫曼编码思想实现对字符串的编码,以及对编码的解码。
字符串的长度不小于5000字节。
读取要编码的文本文件,将文件的内容进行编码,生成新的文件。
对编码文件进行解码,获得文本文件。
将译码的文本文件和原文件进行比较,恢复文件和原文件必须完全一致。
设字符集及频度如下表:
字符
空格
A
B
C
D
E
F
G
H
I
J
K
L
M
频度
186
64
13
22
32
103
21
15
47
57
1
5
32
20
字符
N
O
P
Q
R
S
T
U
V
W
X
Y
Z
频度
57
63
15
1
48
51
80
23
8
18
1
16
1
基本要求与说明
1、根据哈夫曼树编码原理,构造哈夫曼树,创建一套哈夫曼编码
2、读取文本文件,并对文件内容编码,生成编码文件
3、对编码文件进行译码,获得恢复文件
4、比较恢复文件和原文件是否相同。
课程设计任务书
设计题目
哈夫曼编\译码器
学生姓名
刘文杰
所在院系
计算机科学与信息工程学院
专业、年级、班
软件工程16-2
设计要求:
1.根据哈夫曼树编码原理,构造哈夫曼树,创建一套哈夫曼编码。
2.读取文本文件,并对文件内容编码,生成编码文件。
3.对编码文件进行译码,获得恢复文件。
4.比较恢复文件和原文件是否相同。
学生应完成的工作:
1.学生应认真学习参考程序,理解每个文件、每个函数以及各个变量的作用和意义。
在此基础上进一步改进程序,最后正确地运行程序。
2.对程序进行测试,设计详细的测试计划,然后根据测试计划设计测试用例,对程序进行测试。
测试时应注意对各种边缘情况进行测试。
3.完成课程设计报告。
参考文献阅读:
1.严蔚敏.数据结构(C语言版).清华大学出版社,2011
2.谭浩强.C程序设计(第四版).清华大学出版,2010
3.蒋立翔.C++程序设计技能百练[M].中国铁道出版社,2004
工作计划:
1.小组审题,查阅资料,进行设计前的必要资料准备(3天)。
2.把程序完整运行出来(4天)。
3.增加改进程序(3天)。
4.写课程设计报告(3天)。
5.提交课程设计报告及答辩(1天)
任务下达日期:
2017年12月01日
任务完成日期:
2017年12月19日
指导教师(签名):
学生(签名):
哈夫曼编\译码器
摘要:
采用哈夫曼编码思想实现对字符串的编码,以及对编码的解码。
字符串的长度不小于5000字节。
读取要编码的文本文件,将文件的内容进行编码,生成新的文件。
对编码文件进行解码,获得文本文件。
将译码的文本文件和原文件进行比较,恢复文件和原文件必须完全一致。
关键词:
构建哈夫曼树哈弗曼编码哈夫曼译码字符串编码打印编码函数
1.设计背景
1.1哈夫曼树的介绍
HuffmanTree,中文名是哈夫曼树或霍夫曼树或者赫夫曼树,它是最优二叉树。
定义:
给定n个权值作为n个叶子结点,构造一棵二叉树,若树的带权路径长度达到最小,则这棵树被称为哈夫曼树。
(01)路径和路径长度
定义:
在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。
通路中分支的数目称为路径长度。
若规定根结点的层数为1,则从根结点到第L层结点的路径长度为L-1。
(02)结点的权及带权路径长度
定义:
若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。
结点的带权路径长度为:
从根结点到该结点之间的路径长度与该结点的权的乘积。
(03)树的带权路径长度
定义:
树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL。
1.2设计的作用、目的
通过完成具体编码算法的程序设计和调试工作,提高编程能力,深刻理解信源编码、信道编译码的基本思想和目的,掌握编码的基本原理与编码过程,增强逻辑思维能力,培养和提高自学能力以及综合运用所学理论知识去分析解决实际问题的能力,逐步熟悉开展科学实践的程序和方法。
主要目的是加深对理论知识的理解,掌握查阅有关资料的技能,提高实践技能,培养独立分析问题、解决问题及实际应用的能力。
通过课程设计各环节的实践,应达到如下要求:
1.理解无失真信源编码的理论基础,掌握无失真信源编码的基本方法;
2.根据哈夫曼编码算法,考虑一个有多种可能符号(各种符号发生的概率不同)的信源,得到哈夫曼编码和码树;
3.掌握哈夫曼编码的优缺点;
4.通过完成具体编码算法的程序设计和调试工作,提高编程能力,深刻理解信源编码、信道编译码的基本思想和目的,掌握编码的基本原理与编码过程,增强逻辑思维能力,培养和提高自学能力以及综合运用所学理论知识去分析解决实际问题的能力,逐步熟悉开展科学实践的程序和方法。
1.3设计任务及要求
1.理解无失真信源编码的理论基础,掌握无失真信源编码的基本方法;
2.掌握哈夫曼编码/费诺编码方法的基本步骤及优缺点;
3.深刻理解信道编码的基本思想与目的,理解线性分组码的基本原理与编码过程;
2.设计方案
2.1实验内容
假设有n个权值,则构造出的哈夫曼树有n个叶子结点。
n个权值分别设为w1、w2、…、wn,哈夫曼树的构造规则为:
1.将w1、w2、…,wn看成是有n棵树的森林(每棵树仅有一个结点);
2.在森林中选出根结点的权值最小的两棵树进行合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;
3.从森林中删除选取的两棵树,并将新树加入森林;
4.重复(02)、(03)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。
2.2操作思路
以{5,6,7,8,15}为例,来构造一棵哈夫曼树。
第1步:
创建森林,森林包括5棵树,这5棵树的权值分别是5,6,7,8,15。
第2步:
在森林中,选择根节点权值最小的两棵树(5和6)来进行合并,将它们作为一颗新树的左右孩子(谁左谁右无关紧要,这里,我们选择较小的作为左孩子),并且新树的权值是左右孩子的权值之和。
即,新树的权值是11。
然后,将"树5"和"树6"从森林中删除,并将新的树(树11)添加到森林中。
第3步:
在森林中,选择根节点权值最小的两棵树(7和8)来进行合并。
得到的新树的权值是15。
然后,将"树7"和"树8"从森林中删除,并将新的树(树15)添加到森林中。
第4步:
在森林中,选择根节点权值最小的两棵树(11和15)来进行合并。
得到的新树的权值是26。
然后,将"树11"和"树15"从森林中删除,并将新的树(树26)添加到森林中。
第5步:
在森林中,选择根节点权值最小的两棵树(15和26)来进行合并。
得到的新树的权值是41。
然后,将"树15"和"树26"从森林中删除,并将新的树(树41)添加到森林中。
此时,森林中只有一棵树(树41)。
这棵树就是我们需要的哈夫曼树!
3.方案实施
3.1C语言编程
#include
#include
#include
#defineMAX99999
#defineN27//定义最多节点个数
#defineM2*N-1//中间节点个数
typedefstruct
{
intweight;
intparent;
intLChild;
intRChild;
}HTNode,HuffmanTree[M+1];//因为零号单元不使用
typedefchar*HuffmanCode[N+1];
HuffmanCodeco;//创建全局变量用于储存HuffmanCode
charCH[N];
intweight[N]={64,13,22,32,103,21,15,47,57,1,5,32,20,57,63,15,1,48,51,80,23,8,18,1,16,1};
HuffmanTreeht;
charword[30];//全局变量用于储存键入的内容
voidInit_CH()
{
inti;
CH[26]='';
CH[0]='A';
for(i=1;i<26;i++)
CH[i]='A'+i;
for(i=0;i<27;i++)
printf("%c",CH[i]);
}
voidselect(int*sr,int*sl,intn)
{
inti,point;
point=MAX;
for(i=0;i{
if(ht[i+1].weight{
*sr=i+1;//*sr是最小的
point=ht[*sr].weight;
}
}
ht[*sr].parent=1;
point=MAX;
for(i=0;i{
if(ht[i+1].weight{
*sl=i+1;//*sl是第二小
point=ht[*sl].weight;
}
}
ht[*sl].parent=1;
}
voidInitHuffmanCode()
{
inti,sr,sl;
for(i=1;i<=N;i++)
{
ht[i].weight=weight[i-1];
ht[i].parent=0;
ht[i].LChild=0;
ht[i].RChild=0;
}
for(i=N+1;i<=M;i++)
{
ht[i].weight=0;
ht[i].parent=0;
ht[i].LChild=0;
ht[i].RChild=0;
}
printf("------初始化完成------\n");
for(i=N+1;i<=M;i++)
{
select(&sr,&sl,i-1);
ht[i].weight=ht[sr].weight+ht[sl].weight;
ht[sr].parent=i;
ht[sl].parent=i;
ht[i].LChild=sr;
ht[i].RChild=sl;
}
for(i=1;i<=M;i++)
{
printf("%d%d\n",ht[i].parent,i);
}
}
voidCreateHuffmanCode()
{
FILE*trans;
inti,start,p,c;
char*cd;
cd=(char*)malloc(N*sizeof(char));
cd[N-1]='\0';
for(i=1;i<=N;i++)
{
start=N-1;
c=i;
p=ht[i].parent;
while(p)
{
--start;
if(ht[p].LChild==c)
cd[start]='0';
else
cd[start]='1';
c=p;
p=ht[p].parent;
}
co[i]=(char*)malloc((N-start)*sizeof(char));
strcpy(co[i],&cd[start]);
printf("%s%d\n",co[i],i);
}
if((trans=fopen("C:
trans.txt","w"))==NULL)
{
printf("cannotopentrans.txt!
");
exit(0);
}
fputs("------哈夫曼编码表初始化如下------\n",trans);
for(i=0;i{
fputc(CH[i],trans);
fputs(":
",trans);
fputs(co[i+1],trans);
fputs("\n",trans);
}
fclose(trans);
}
voidInputHuffmanWord()
{
FILE*fp;
if((fp=fopen("C:
storeWord.txt","w"))==NULL)
{
printf("cannotopenstoreWord.txt!
");
exit(0);
}
printf("请输入以'#'结束的大写字母字符串:
\n");
while(strcmp(word,"#")!
=0)
{
fputs(word,fp);
gets(word);
}
fclose(fp);
}
//选择原码输入类型
voidSelectInputType()
{
system("cls");
intpoint;
while
(1)
{
printf("'0':
从键盘键入编码内容\n");
printf("'1':
从文件中读取编码内容\n");
printf("'2':
退出\n");
scanf("%d",&point);
system("cls");
switch(point)
{
case0:
InputHuffmanWord();break;
case1:
printf("将编码内容保存在storeWord.txt文件即可。
\n");
printf("请进行下一步操作!
\n");
break;
case2:
printf("已经退出输入编码内容。
。
。
\n");return;
default:
printf("Inputerror!
\n");break;
}
}
}
//编码
voidHuffman_Encod()
{
intp=M,i,flag;
FILE*Input,*Output;
charch;
if((Output=fopen("C:
storeWord.txt","r"))==NULL)
{
printf("cannotopenstore.txt!
");
exit(0);
}
if((Input=fopen("C:
storeCode.txt","w"))==NULL)
{
printf("cannotopenstoreCode.txt!
");
exit(0);
}
while(!
feof(Output))//遇到输入文件的结束标识
{
flag=0;
ch=fgetc(Output);
//putchar(ch);
for(i=0;i{
if(CH[i]==ch)
{
fputs(co[i+1],Input);
flag=1;
}
}
if(flag==0)
{
fputc('*',Input);
}
}
fclose(Output);
fclose(Input);
}
//译码
voidHuffman_Decod()
{
FILE*fp,*Input;
intp=M,i=0;
charch;
if((fp=fopen("C:
storeCode.txt","r"))==NULL)
{
printf("cannotopenstoreCode.txt!
");
exit(0);
}
if((Input=fopen("C:
storeWord_1.txt","w"))==NULL)
{
printf("cannotopenstoreWord_1.txt!
");
exit(0);
}
ch=fgetc(fp);
while(!
feof(fp))
{
if(ch=='0')
p=ht[p].LChild;
elseif(ch=='1')
p=ht[p].RChild;
elseif(ch=='*')
{
fputs("*",Input);
putchar(ch);
}
else
printf("Inputerror!
");
if(ht[p].LChild==0&&ht[p].RChild==0)
{
fputc(CH[p-1],Input);
putchar(CH[p-1]);
p=M;
}
ch=fgetc(fp);
}
fclose(fp);
fclose(Input);
printf("\n");
}
//译码与原码比较
voidCompare_word()
{
charch_1,ch_2;
intflag=1;
FILE*origin,*decod;
if((origin=fopen("C:
storeWord.txt","r"))==NULL)
{
printf("cannotopenstoreCode.txt!
");
exit(0);
}
if((decod=fopen("C:
storeWord_1.txt","r"))==NULL)
{
printf("cannotopenstoreWord_1.txt!
");
exit(0);
}
while(!
feof(decod))
{
ch_1=getc(decod);
ch_2=getc(origin);
if(ch_1!
='*')
{
if(ch_1!
=ch_2)
flag=0;
}
}
fclose(decod);
fclose(origin);
if(flag==0)
printf("原码与译码不相符!
\n");
else
printf("原码与译码相符!
\n");
}
voidmain()
{
intpoint;
Init_CH();
InitHuffmanCode();
CreateHuffmanCode();
while
(1)
{
printf("******哈夫曼编译器******\n");
printf("\n'0':
初始化哈夫曼表\n");
printf("'1':
输入编码内容\n");
printf("'2':
开始编码\n");
printf("'3':
开始译码\n");
printf("'4':
与译码与原码比较\n");
printf("'5':
退出哈夫曼编译器\n");
scanf("%d",&point);
system("cls");
switch(point)
{
case0:
printf("哈夫曼表初始化完成!
\n请进行下一步操作!
\n");
break;
case1:
SelectInputType();
break;
case2:
Huffman_Encod();
printf("编码结束!
请进行下一步操作!
\n");
break;
case3:
Huffman_Decod();
printf("译码结束!
已将译码内容存放storeWord_1.txt!
\n");
printf("请进行下一步操作!
\n");
break;
case4:
Compare_word();
break;
case5:
printf("已经退出编译器。
。
。
\n");
return;
default:
printf("Inputerror!
\n");break;
}
}
}
3.2程序介绍
本程序的编码和运行都是在VisualC++6.0中实现的,在VisualStdio中也能实现,整个程序虽然看似庞大,但编写过程清晰,采用模块化编写,各个问题逐个击破,也方便对程序的管理和运行。
整个程序的编写分为五大部分,五大部分紧密相连,环环相扣,共同实现程序的编码。
在上述存储结构上实现的哈夫曼算法可大致描述为(设T的类型为HuffmanTree):
(1)初始化
将T[0..m-1]中2n-1个结点里的三个指针均置为空(即置为-1),权值置为0。
(2)输入
读入n个叶子的权值存于向量的前n个分量(即T[0..n-1])中。
它们是初始森林中n个孤立的根结点上的权值。
(3)合并
对森林中的树共进行n-1次合并,所产生的新结点依次放人向量T的第i个分量中(n≤i≤m-1)。
每次合并分两步:
①在当前森林T[0..i-1]的所有结点中,选取权最小和次小的两个根结点[p1]和T[p2]作为合并对象,这里0≤p1,p2≤i-1。
②将根为T[p1]和T[p2]的两棵树作为左右子树合并为一棵新的树,新树的根是新结点T[i]。
具体操作:
将T[p1]和T[p2]的parent置为i,
将T[i]的lchild和rchild分别置为p1和p2
新结点T[i]的权值置为T[p1]和T[p2]的权值之和。
注意:
合并后T[pl]和T[p2]在当前森林中已不再是根,因为它们的双亲指针均已指向了T[i],所以下一次合并时不会被选中为合并对象。
3.3程序流程图以及说明
主程序
N
结束
定义全局数组a,b,c,d定义全局变量
定义变量n,x,y,K,
开始
输出编码过程中产生的新概率
输出码字
输出平均码长、
信源熵,编码效率,冗余度
初始
输出提示获取
y=xiaoxi()
ordination(m,a);
Y
数组a一维,存放输入概率
数组b,二维存放编码过程概率
数组c,三维,存放编码每个位置即时编码;
数组d,一维存放码长