哈夫曼编译码实验指导书Word文件下载.docx
《哈夫曼编译码实验指导书Word文件下载.docx》由会员分享,可在线阅读,更多相关《哈夫曼编译码实验指导书Word文件下载.docx(20页珍藏版)》请在冰豆网上搜索。
C1110D1111
E110F00
G0111H010
五、实现
1、哈夫曼树的存储结构
根据哈夫曼树的构造算法,哈夫曼数除叶结点外,其余结点的度均为2。
对于具有n个权值构造的哈夫曼树,根据二叉树的性质3,哈夫曼树的结点总数为m=2n-1,即哈夫曼树所需存储空间是由文本中不同字符的个数唯一确定的。
为了便于对多棵二叉树进行组织和便于查找各二叉树的根结点,采用静态链表作为二叉树的存储结构。
其存储结构描述如下:
typedefstruc{
charch;
unsignedintweight;
unsignedintparent,lchild,rchild;
}HTNode,*HuffmanTree;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
A
-1
B
29
C
D
E
F
23
G
H
*
15
19
42
58
100
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>
记录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)主函数:
功能:
通过菜单选择控制对系统功能的操作
(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)译码函数:
函数格式voidDecode()
从文件EnCodeFileName中读入“0”和“1”表示的哈夫曼编码数据,将其转换为文本字符,并将译码结果写入文件DecodeFileName中。
(6)显示哈夫曼树函数:
PrintHuffmanTree
函数格式voidPrintHuffmanTree()
以凹式形式显示哈夫曼树
(7)显示哈夫曼树表函数:
函数格式voidPrintHuffmanTable()
(8)显示字符哈夫曼编码函数:
PrintCharCoding
函数格式voidPrintCharCoding()
显示字符的哈夫曼编码
(9)读文件函数:
函数格式intReadFromFile()
从文件TableFileName中读哈夫曼树数据
0—读数据失败
>
0--读入的数据个数
(10)选择根界点函数:
函数格式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].weight<
ht[*s1].weight
while(i<
=n)
i++
*s2=0
++(*s2)
*s2=j
j=(*s2)+1
j!
=*s1&
ht[j].parent==-1&
ht[j].weight<
ht[*s2].weight
while(*s2<
(ht[*s2].parent!
=-1||*s2==*s1)*p&
j++
七、思考题
1、若字符的编码不用“0”和“1”的字符表示,而采用0和1的字节二进制位表示,该如何实现?
2、凹式形式显示哈夫曼树的算法若采用先序遍历的递归算法,该如何实现?
八、部分函数代码
#include<
stdio.h>
/*forsize_t,printf()*/
conio.h>
/*forgetch()*/
ctype.h>
/*fortolower()*/
malloc.h>
/*formalloc(),calloc(),free()*/
string.h>
/*formemmove(),strcpy()*/
/*树结构和全局结构指针*/
#defineNODENUM26
/*----------哈夫曼树结点结构-------------*/
structnode
{
intweight;
intparent;
intlchild,rchild;
}*ht;
//指向哈夫曼树的存储空间的指针变量
/*----------字符编码结点结构-------------*/
/*--------哈夫曼树遍历时栈的结点结构------*/
/*---------------常量文件名---------------*/
constchar*TableFileName="
//哈夫曼树数据文件
constchar*CodeFileName="
//字符编码数据文件
constchar*SourceFileName="
//需编码的字符串文件
//编码数据文件
//译码字符文件
/************************************************************/
/*释放哈夫曼树数据空间函数*/
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;
(学生完成)
/*创建哈夫曼树和产生字符编码的函数*/
inti=0,n,m,j,f,s1,s2,start;
charcd[NODENUM];
structHuffmanCodingcode[NODENUM];
printf("
输入字符总数n:
"
);
scanf("
%d"
&
n);
EatCharsUntilNewLine();
m=2*n-1;
//申请哈夫曼树的存储空间
/********************************************************/
/*创建哈夫曼树*/
/*1、输入字符和权值*/
/*2、初始化哈夫曼树*/
/*3、建立哈夫曼树*/
/********************************************************/
//把哈夫曼树的数据存储到文件中
wb"
{printf("
return;
fwrite(&
fwrite(ht,sizeof(structnode),m,fp);
/*********************************************************/
/*产生字符编码*/
/*从页结点开始,沿父结点上升,直到根结点,若沿*/
/*父结点的左分支上升,则得编码字符“0”,若沿父结*/
/*点的右分支上升,则得编码字符“1”*/
/*******************