哈夫曼编译码实验指导书.docx
《哈夫曼编译码实验指导书.docx》由会员分享,可在线阅读,更多相关《哈夫曼编译码实验指导书.docx(20页珍藏版)》请在冰豆网上搜索。
哈夫曼编译码实验指导书
实验七哈夫曼编/译码器
一、实验目的
通过哈夫曼树的构造,深刻理解二叉树的构造。
通过哈夫曼编/译码过程,深刻领会二叉树的基本操作和二叉树的应用,帮助学生熟练掌握二叉数组织数据的基本原理和对二叉数操作的实现方法。
二、实验内容
本实验的主要内容是:
1、由文本字符及字符在文本文件中出现的频率,构造带权路径最短的最优二叉树(哈夫曼树),并依此为基础构造字符的前缀编码(哈夫曼编码);
2、编码:
从文本文件中读入文本字符,按照已知的字符哈夫曼编码将文本字符转换为二进制串的哈夫曼编码形式。
3、译码:
从文件中读入二进制串字符,按照哈夫曼树将其转换为文本字符。
4、输出哈夫曼树:
以凹入表(层次表)的形式显示哈夫曼树。
三、实验仪器
微型计算机
实验用编程语言:
TurboC2.0,BorlandC3.0等以上版本
四、实验原理
1、哈夫曼树的定义
哈夫曼树(最优二叉树):
设有n个权值{w1,w2,...,wn},试构造一棵有n个叶结点的二叉树,第i个叶结点的权值为wi,则其中带权路径长度为最小的二叉树被称为最优二叉树或哈夫曼树。
2、哈夫曼算法:
哈夫曼算法要点是:
(1)根据给定的n个权值{w1,w2,...,wn}构成n棵二叉树的集合F={T1,T2,...,Tn},其中每棵二叉树Ti只有一个带权为Wi的根结点,左右子树为空。
(2)在F中选取两棵根结点的权值最小的树作为左右子树构造一棵新的二叉树,且置新的二叉树根结点的权值为左右子树根结点权值之和。
(3)在F中删除这两棵树,同时将新得到的树加入到F中。
(4)重复
(2)和(3),直到只剩下一棵二叉树为止。
这棵二叉树便是哈夫曼树。
3、哈夫曼编码
对于字符的二进制编码,若任一字符的二进制编码都不是另一个字符的二进制编码的前缀。
这种编码叫做前缀编码。
以n种字符出现的频率作权,设计一棵哈夫曼树,并用二叉树的叶结点分别表示待编码的字符,并约定左分支表示字符‘0’,右分支表示字符‘1’。
则对每个叶结点,都有唯一的一条从根结点出发的路径,则该路径上分支字符组成的字符串作为该叶子结点的编码。
由此得到的编码必为二进制的前缀编码,而且是编码总长最短的二进制前缀编码,这种编码即为哈夫曼编码。
例:
设有8个字符{A,B,C,D,E,F,G,H},其概率为{0.05,0.29,0.07,0.08,0.14,0.23,0.03,0.11},设其权值用整数表示为{5,29,7,8,14,23,3,11}
●100
●42●58
●23●19●29●29
●11●8●14●15
●5●3●7●8
则字符的哈夫曼编码为:
A0110B10
C1110D1111
E110F00
G0111H010
五、实现
1、哈夫曼树的存储结构
根据哈夫曼树的构造算法,哈夫曼数除叶结点外,其余结点的度均为2。
对于具有n个权值构造的哈夫曼树,根据二叉树的性质3,哈夫曼树的结点总数为m=2n-1,即哈夫曼树所需存储空间是由文本中不同字符的个数唯一确定的。
为了便于对多棵二叉树进行组织和便于查找各二叉树的根结点,采用静态链表作为二叉树的存储结构。
其存储结构描述如下:
typedefstruc{
charch;
unsignedintweight;
unsignedintparent,lchild,rchild;
}HTNode,*HuffmanTree;
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
A
5
8
-1
-1
B
29
13
-1
-1
C
7
9
-1
-1
D
8
9
-1
-1
E
14
11
-1
-1
F
23
12
-1
-1
G
3
8
-1
-1
H
11
10
-1
-1
*
8
10
6
0
*
15
11
2
3
*
19
12
8
7
*
29
13
4
9
*
42
14
10
5
*
58
1
1
11
*
100
-1
12
13
2、哈夫曼编码的存储结构
若要编码的文本文件的字符集不变,则其哈夫曼编码不变。
字符的哈夫曼编码一旦确定,可以长期使用。
因此,需要用文件同时保存字符的哈夫曼编码和字符。
哈夫曼编码的表示形式既要考虑存储空间的效率,也要考虑文件读取的方便。
文本字符的哈夫曼编码是不等长的二进制码,用不等长的二进制字符串表示,节省存储空间,但用文件读取不方便。
若用等长的结构体表示,可能要浪费一点存储空间,但文件存取方便。
为了便于文件存取,采用等长结构体表示哈夫曼具有可取之处,其存储结构描述如下:
#defineNODENUM26//字符集
structHuffmanCoding
{charch;//字符
charcoding[NODENUM];//字符编码
};
3、字符及权值的输入形式
为了避免字符及其权值的手工键盘输入带来的错误,可以将字符及其权值组织成文本文件的形式。
文本文件的格式为:
字符权值
例如:
A5
B19
C7
D8
E14
F23
G3
H11
一般读入单个字符很不方便,格式化输入字符串和数值型数据很方便,所以字符数据可以采用读串的方式读入,然后把它赋给字符变量。
4、文件的设置
(1)字符权值文件
constchar*WeighFileName="Weight.txt";
存放需构造哈夫曼树的字符和权值数据,为文本数据,见“字符及权值的输入形式”。
(2)哈夫曼树数据文件
constchar*TableFileName="HfmTbl.txt";
存放哈夫曼树数据,二进制HTNode结构型。
格式为:
<数据个数M><记录1><记录2>……<记录M>
数据个数—哈夫曼树的结点数,M=2n-1,n为权值个数
记录i--二进制HTNode结构型数据
(3)字符编码数据文件
constchar*CodeFileName="CodeFile.txt";
存放字符编码数据,二进制structHuffmanCoding结构型。
格式为:
<数据个数n><记录1><记录2>……<记录n>
数据个数—权值个数
记录i--二进制structHuffmanCoding结构型数据
(4)文本文件
constchar*SourceFileName="SrcText.txt";
存放需编码的文本字符串数据,其中的字符属于编码字符集。
(5)编码数据文件
constchar*EnCodeFileName="EnCodeFile.txt";
存放对文本文件编码后的数据,其中的数据为“0”和“1”的字符串。
(6)译码字符文件
constchar*DecodeFileName="DecodeFile.txt";
存放译码后的字符文件
5、程序基本功能
(1)初始化:
输入编码字符和其权值,生成哈夫曼树和字符的哈夫曼编码,并用文件保存哈夫曼树和字符的哈夫曼编码。
(2)编码:
把文本字符串转换为“0”和“1”表示的哈夫曼编码。
(3)译码:
把“0”和“1”表示的哈夫曼编码串转换为文本字符串
(4)显示哈夫曼树:
以凹入形式显示哈夫曼树。
(5)显示哈夫曼表:
以表格形式显示哈夫曼树
(6)显示字符编码
6、辅助功能
(1)菜单选择:
将上述功能通过“菜单”形式罗列出来,通过菜单选择进行交互式控制程序运行。
(2)读文件:
把哈夫曼树数据读入内存。
(3)选择结点:
选择两个具有最小权值的根结点。
7、程序结构
本程序可以由10个函数组成,其中主函数1个,基本功能函数6个,辅助功能函数3个。
函数间的调用关系图2所示。
main
nemu
PrintCharCoding
PrintHuffmanTable
PrintHuffmanTree
Decode
Encode
Initialization
ReadFromFile
Select
图1:
程序结构示意图
8、程序函数
(1)主函数:
main
功能:
通过菜单选择控制对系统功能的操作
(2)菜单选择函数:
menu
函数格式:
intmenu(void)
函数功能:
构造功能菜单,并选择下一步要操作的功能。
函数参数:
无参数。
函数返回值:
1~7中的一个序号。
可供选择的功能如下:
1---Initialization
2---Encode
3---Decode
4---Printhuffmantree
5---PrinthuffmanTable
6---PrintcharCoding
7---Quit
(3)初始化函数:
Initialization
函数格式:
voidInitialization()
函数参数:
无参数
函数功能:
输入编码字符和权值,生成哈夫曼树和字符编码,并用文件TableFileName和CodeFileName保存哈夫曼树和字符编码数据。
函数返回值:
无
(4)文本串编码函数:
Encode
函数格式voidEncode(void)
函数功能:
从文本串文件SourceFileName中读入文本字符,按照CodeFileName文件中字符的编码将其转换为“0”和“1”表示的哈夫曼编码,并把编码结果写入文件EnCodeFileName中。
函数参数:
无
函数返回值:
无
(5)译码函数:
Decode
函数格式voidDecode()
函数功能:
从文件EnCodeFileName中读入“0”和“1”表示的哈夫曼编码数据,将其转换为文本字符,并将译码结果写入文件DecodeFileName中。
函数参数:
无
函数返回值:
无
(6)显示哈夫曼树函数:
PrintHuffmanTree
函数格式voidPrintHuffmanTree()
函数功能:
以凹式形式显示哈夫曼树
函数参数:
无
函数返回值:
无
(7)显示哈夫曼树表函数:
PrintHuffmanTable
函数格式voidPrintHuffmanTable()
函数功能:
以表格形式显示哈夫曼树
函数参数:
无
函数返回值:
无
(8)显示字符哈夫曼编码函数:
PrintCharCoding
函数格式voidPrintCharCoding()
函数功能:
显示字符的哈夫曼编码
函数参数:
无
函数返回值:
无
(9)读文件函数:
ReadFromFile
函数格式intReadFromFile()
函数功能:
从文件TableFileName中读哈夫曼树数据
函数参数:
无
函数返回值:
0—读数据失败
>0--读入的数据个数
(10)选择根界点函数:
Select
函数格式voidSelect(structnodeht[],intn,int*s1,int*s2)
函数功能:
从多棵树中选择两个权值最小的根结点
函数参数:
structnodeht[]—哈夫曼树
intn—选择结点的范围,即只能在0~n中选择结点
int*s1—指向第一个权值最小的结点的指针
int*s2—指向第二个权值最小的结点的指针
函数返回值:
无
六、主要算法描述
1、初始化函数Initialization算法描述
功能:
读入字符及其权值,生成哈夫曼树和字符哈夫曼编码。
字符输入的处理:
C语言输入字符的处理很不方便,任何一个字符都当作有效字符处理,包括空格字符和回车符。
而用格式读函数读字符串很方便,空格字符和回车符都当作字符串数据的分隔。
因此,可以先把字符用格式读函数把字符读入字符数组中,再将其赋值给字符变量,这样处理更简单。
算法步骤:
(1)输入:
读入n个叶结点的字符和权值存放于静态树T中的前n个分量中。
(2)初始化:
将树T的其余结点的三个指针均置为空(-1),权值置为0,字符为“*”。
(3)合并:
进行n-1次合并,将产生的新结点i依次放入T的第i个分量中(n≤i≤m-1)。
合并分两步进行:
①在当前森林T[0..i-1]的所有结点中,选取权最小和次小的两个根结点T[p1]和T[p2]作为合并对象。
(0≤p1,p2≤i-1)
②将根为T[p1]和T[p2]的两棵树作为左右子树合并为一棵新的树。
新树的根为T[i],权值为T[p1]和T[p2]的权值之和。
并且T[p1]和T[p2]的parent为i,T[i]的lchild和rchild分别为p1和p2。
(4)保存哈夫曼树数据。
(5)生成字符编码。
(6)保存字符编码数据。
算法描述如下:
code[i].ch=ht[i].ch;strcpy(code[i].coding,&cd[start])
2、编码函数Encode算法描述
原理:
对被编码文件中的每个字符,在编码数据表中查找,若字符在编码数据表中,则取出字符编码放入结果文件中。
算法描述如下:
return
3、译码函数Decode算法描述
译码原理:
依次从文件中读入“0”和“1”串,从哈夫曼树根结点开始,若读入的是“0”,则指针以到根结点的左孩子结点;若为“1”,则指针以到根结点的右孩子结点。
重复该过程,直到指针达到哈夫曼树的叶结点,输出该叶结点对应的字符,即完成一个字符的译码。
算法描述如下图所示。
关闭文件
3、显示哈夫曼树函数PrintHuffmanTree算法描述
原理:
显示哈夫曼树的过程实际上就是按先根顺序输出哈夫曼树结点的过程。
如果要按凹式形式输出结点,不仅要知道结点的树出顺序,而且要知道结点的层次,通过结点的层次可以计算出输出内容凹进的格数。
在先序遍历的非递归算法中,把结点的层次也作为栈元素的基本内容就可以解决该问题。
栈的结点结构定义如下:
structstacknode{
intNodeLevel;//结点的层次
intNodeElem;//结点的序号
};
采用顺序栈存储结构。
算法描述如下:
top--node=stack[top].NodeElemlevel=stack[top].NodeLevel
4、选择具有最小权值的两个根结点的函数Select算法描述
原理:
根结点的条件是指向父结点的指针为空。
选择第一个具有最小权值的跟结点时,首先要找到第一个根结点,即父结点指针为空的结点,再以次为基础找具有最小权值的根结点。
选择第二个具有最小权值的跟结点时,首先要找到第一个根结点,即父结点指针为空,而且不是第一个被选择的的结点,在以次为基础找具有最小权值的根结点时,同样要注意,满足条件的结点既是根结点,又不是第一个已选结点。
算法描述如下图所式。
接口参数:
structnodeht[],intn,int*s1,int*s2
*s1=0
while(*s1<=n&&ht[*s1].parent!
=-1)*p&&*p==‘’
++(*s1)
i=(*s1)+1
*s1=i
ht[i].parent==-1&&ht[i].weightwhile(i<=n)
i++
*s2=0
++(*s2)
*s2=j
while(i<=n)
j=(*s2)+1
j!
=*s1&&ht[j].parent==-1&&ht[j].weightwhile(*s2<=n&&(ht[*s2].parent!
=-1||*s2==*s1)*p&&*p==‘’
j++
七、思考题
1、若字符的编码不用“0”和“1”的字符表示,而采用0和1的字节二进制位表示,该如何实现?
2、凹式形式显示哈夫曼树的算法若采用先序遍历的递归算法,该如何实现?
八、部分函数代码
#include/*forsize_t,printf()*/
#include/*forgetch()*/
#include/*fortolower()*/
#include/*formalloc(),calloc(),free()*/
#include/*formemmove(),strcpy()*/
/*树结构和全局结构指针*/
#defineNODENUM26
/*----------哈夫曼树结点结构-------------*/
structnode
{
charch;
intweight;
intparent;
intlchild,rchild;
}*ht;//指向哈夫曼树的存储空间的指针变量
/*----------字符编码结点结构-------------*/
structHuffmanCoding
{charch;
charcoding[NODENUM];
};
/*--------哈夫曼树遍历时栈的结点结构------*/
structstacknode{
intNodeLevel;
intNodeElem;
};
/*---------------常量文件名---------------*/
constchar*TableFileName="HfmTbl.txt";//哈夫曼树数据文件
constchar*CodeFileName="CodeFile.txt";//字符编码数据文件
constchar*SourceFileName="SrcText.txt";//需编码的字符串文件
constchar*EnCodeFileName="EnCodeFile.txt";//编码数据文件
constchar*DecodeFileName="DecodeFile.txt";//译码字符文件
/************************************************************/
/*释放哈夫曼树数据空间函数*/
/************************************************************/
voidfree_ht()
{
if(ht!
=NULL)
{
free(ht);
ht=NULL;
}
}
/************************************************************/
/*从文件读取哈夫曼树数据函数*/
/************************************************************/
intReadFromFile()
{
inti;
intm;
FILE*fp;
if((fp=fopen(TableFileName,"rb"))==NULL)
{printf("cannotopen%s\n",TableFileName);
getch();
return0;
}
fread(&m,sizeof(int),1,fp);//m为数据个数
free_ht();
ht=(structnode*)malloc(m*sizeof(structnode));
fread(ht,sizeof(structnode),m,fp);
fclose(fp);
returnm;
}
/************************************************************/
/*吃掉无效的垃圾字符函数函数*/
/*从键盘读字符数据时使用,避免读到无效字符*/
/************************************************************/
voidEatCharsUntilNewLine()
{
while(getchar()!
='\n')continue;
}
/************************************************************/
/*选择权值最小的两个根结点函数*/
/************************************************************/
voidSelect(structnodeht[],intn,int*s1,int*s2)
{inti,j;
(学生完成)
}
/************************************************************/
/*创建哈夫曼树和产生字符编码的函数*/
/************************************************************/
voidInitialization()
{
inti=0,n,m,j,f,s1,s2,start;
charcd[NODENUM];
structHuffmanCodingcode[NODENUM];
FILE*fp;
printf("输入字符总数n:
");
scanf("%d",&n);
EatCharsUntilNewLine();
m=2*n-1;
ht=(structnode*)malloc(m*sizeof(structnode));
//申请哈夫曼树的存储空间
/********************************************************/
/*创建哈夫曼树*/
/*1、输入字符和权值*/
/*2、初始化哈夫曼树*/
/*3、建立哈夫曼树*/
/********************************************************/
//把哈夫曼树的数据存储到文件中
if((fp=fopen(TableFileName,"wb"))==NULL)
{printf("cannotopen%s\n",TableFileName);
getch();
return;
}
fwrite(&m,sizeof(int),1,fp);
fwrite(ht,sizeof(structnode),m,fp);
fclose(fp);
/*********************************************************/
/*产生字符编码*/
/*从页结点开始,沿父结点上升,直到根结点,若沿*/
/*父结点的左分支上升,则得编码字符“0”,若沿父结*/
/*点的右分支上升,则得编码字符“1”*/
/*******************