**
◆5.2③哈夫曼编/译码器
**
[问题描述]
利用哈夫曼编码进行通信可以大大提高信道利用率,缩短信息传输时间,降低传输成
本。但是,这要求在发送端通过一个编码系统对待传数据预先编码,在接收端将传来的数
据进行译码(复原)。对于双工信道(即可以双向传输信息的信道),每端都需要一个完整的
编/译码系统。试为这样的信息收发站写一个哈夫曼码的编/译码系统。
[基本要求]
一个完整的系统应具有以下功能:
(1) I:初始化(Initialization)。从终端读入字符集大小n,以及n个字符和n个权值, .
建立哈夫曼树,并将它存于文件hfmTree中。
(2) E:编码(Encoding)。利用已建好的哈夫曼树(如不在内存,则从文件hfmTree中
读入),对文件ToBeTran中的正文进行编码,然后将结果存入文件CodeFile中。
(3)D:译码(Decoding)。利用已建好的哈夫曼树将文件CodeFile中的代码进行译
码,结果存入文件TextFile中。
(4)P:印代码文件(Print)。将文件CodeFile以紧凑格式显示在终端上,每行50个代
码。同时将此字符形式的编码文件写入文件CodePrin中。
(5)T:印哈夫曼树(Treeprinting)。将已在内存中的哈夫曼树以直观的方式(树或凹
人表形式)显示在终端上,同时将此字符形式的哈夫曼树写入文件TreePrint中。
[测试数据]
(1)利用教科书例6-2中的数据调试程序。
(2)用下表给出的字符集和频度的实际统计数据建立哈夫曼树,并实现以下报文的
编码和译码:“THIS PROGRAM IS MY FAVORITE"。
[实现提示]
(1)编码结果以文本方式存储在文件CodeFile中。
(2)用户界面可以设计为“菜单”方式:显示上述功能符号,再加上“Q”,表示退出运行Quit。请用户键入一个选择功能符。此功能执行完毕后再显示此菜单,直至某次用户选
择了“Q”为止。
(3)在程序的一次执行过程中,第一次执行I,D或C命令之后,哈夫曼树已经在内存
了,不必再读入。每次执行中不一定执行I命令,因为文件hfmTree可能早已建好。
[选作内容]
(1),上述文件CodeFile中的每个“0”或“1”实际上占用了一个字节的空间,只起到示
意或模拟的作用。为最大限度地利用码点存储能力,试改写你的系统,将编码结果以二进
制形式存放在文件CodeFile中。
(2)修改你的系统,实现对你的系统的原程序的编码和译码(主要是将行尾符编/译
码问题)。
(3)实现各个转换操作的源/目文件,均由用户在选择此操作时指定。
实习报告5.2题 哈夫曼编/译码
一、 需求分析
- 为信息收发站写一个哈夫曼码的编/译码系统
- (1) I:初始化(Initialization)。从终端读入字符集大小n,以及n个字符和n个权值, .
建立哈夫曼树,并将它存于文件hfmTree中。
(2) E:编码(Encoding)。利用已建好的哈夫曼树(如不在内存,则从文件hfmTree中
读入),对文件ToBeTran中的正文进行编码,然后将结果存入文件CodeFile中。
(3)D:译码(Decoding)。利用已建好的哈夫曼树将文件CodeFile中的代码进行译
码,结果存入文件TextFile中。
(4)P:印代码文件(Print)。将文件CodeFile以紧凑格式显示在终端上,每行50个代
码。同时将此字符形式的编码文件写入文件CodePrin中。
(5)T:印哈夫曼树(Treeprinting)。将已在内存中的哈夫曼树以直观的方式(树或凹
人表形式)显示在终端上,同时将此字符形式的哈夫曼树写入文件TreePrint中。 - 用下表给出的字符集和频度的实际统计数据建立哈夫曼树,并实现以下报文的
二、 概要设计
1)
Initialization()
//从终端读入字符集大小n,以及n个字符和n个权值, .
//建立哈夫曼树,并将它存于文件hfmTree中。
Encoding()
//利用已建好的哈夫曼树(如不在内存,则从文件hfmTree中
//读入),对文件ToBeTran中的正文进行编码,然后将结果存入文件CodeFile中。
Decoding()
//利用已建好的哈夫曼树将文件CodeFile中的代码进行译
//码,结果存入文件TextFile中。
Print()
//将文件CodeFile以紧凑格式显示在终端上,每行50个代
//码。同时将此字符形式的编码文件写入文件CodePrin中。
Treeprinting()
//将已在内存中的哈夫曼树以直观的方式(树或凹人表形式)显示在终端上,同时//将此字符形式的哈夫曼树写入文件TreePrint中。
2) 本程序包含七个模块
5. 主程序模块:
void main( ) {
初始化;
do {
接受命令;
处理命令;
} while (“命令”!=“退出”);
}
6. 初始化(Initialization)模块——从终端读入字符集大小n,以及n个字符和n个权值, .
建立哈夫曼树,并将它存于文件hfmTree中。
7. 编码(Encoding) 模块——利用已建好的哈夫曼树(如不在内存,则从文件hfmTree中
读入),对文件ToBeTran中的正文进行编码,然后将结果存入文件CodeFile中。
8. 译码(Decoding) 模块——利用已建好的哈夫曼树将文件CodeFile中的代码进行译
码,结果存入文件TextFile中。
印代码文件(Print) 模块——将文件CodeFile以紧凑格式显示在终端上,每行50个代
码。同时将此字符形式的编码文件写入文件CodePrin中。
9. 印哈夫曼树(Treeprinting) 模块——将已在内存中的哈夫曼树以直观的方式(树或凹
人表形式)显示在终端上,同时将此字符形式的哈夫曼树写入文件TreePrint中。
三、 详细设计
源代码:
//head.h
#ifndef __TEST_H__
#define __TEST_H__
#include<time.h>
#include<string.h>
#include<malloc.h> /* malloc()等 */
#include<limits.h> /* INT_MAX等 */
#include<stdio.h> /* EOF(=^Z或F6),NULL */
#include<stdlib.h> /* atoi() */
#include<math.h> /* floor(),ceil(),abs() */
#include<process.h> // exit()
// 函数结果状态代码
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
#include<dos.h>
typedef struct
{
char c;
unsigned int weight;
unsigned int parent,lchild,rchild;
}HTNode,*HuffmanTree; //动态分配数组存储哈夫曼树
typedef char** HuffmanCode;
typedef struct HuffmanChar
{
unsigned int weight;
char c;
}HuffmanChar;
void menu();
void Initialization();
void ntof(HuffmanTree HT,int n);
void scanfchar(HuffmanChar** hchararray,int n);
void HuffmanCoding(HuffmanTree* HT,HuffmanChar* hchararray,int n);
void Select(HuffmanTree HT,int n,int* s1,int* s2);
void Encoding();
void fton(HuffmanTree* HT,int* n);
void ttoa(HuffmanTree HT,int n,HuffmanCode *HC);
void Decoding();
void Print();
void Treeprinting();
void HuffmanTreeReverse(HuffmanTree HT,int posision,int blanknumber,FILE* pTreePrint,char flag);
#endif
#include"head.h"
#include"Initialization.h"
#include"Encoding.h"
#include"Decoding.h"
#include"Print.h"
#include"Treeprinting.h"
int main()
{
//system("cls"); //清屏函数。
char c;
do{
printf("请选择以下一个功能:\n");
menu();
c=getchar();
system("cls"); //清屏函数。
switch(c)
{
case'I':
Initialization();
system("pause");
break;
case'E':
Encoding();
system("pause");
break;
case'D':
Decoding();
system("pause");
break;
case'P':
Print();
system("pause");
break;
case'T':
Treeprinting();
system("pause");
break;
case'Q':
break;
default:
{
printf("输入错误!\n");
fflush(stdin);
system("pause");
}
fflush(stdin);
}
fflush(stdin);
system("cls"); //清屏函数。
}while(c!='Q');
return 0;
}
void menu()
{
printf("I:初始化(Initialization)\n");
printf("E:编码(Encoding)\n");
printf("D:译码(Decoding)\n");
printf("P:印代码文件(Print)\n");
printf("T:印哈夫曼树(Treeprinting)\n");
printf("Q:退出(Quiting)\n");
}
// (5)T:印哈夫曼树(Treeprinting)。将已在内存中的哈夫曼树以直观的方式(树或凹
// 入表形式)显示在终端上,同时将此字符形式的哈夫曼树写入文件TreePrint中。
void Treeprinting()
{
FILE* pTreePrint=fopen("C:/tmp/TreePrint.txt","w");
int blanknumber=0;
HuffmanTree HT;
int n;
fton(&HT,&n);
int size=2*n-1;
HuffmanTreeReverse(HT,size,blanknumber,pTreePrint,'M');
fclose(pTreePrint);
}
void HuffmanTreeReverse(HuffmanTree HT,int posision,int blanknumber,FILE* pTreePrint,char flag)
{
int i;
if(posision==0) return;
for(i=0;i<blanknumber;i++)
{
putchar(' ');
fputc(' ',pTreePrint);
putchar(' ');
fputc(' ',pTreePrint);
putchar(' ');
fputc(' ',pTreePrint);
putchar(' ');
fputc(' ',pTreePrint);
}
putchar(HT[posision].c);
fputc(HT[posision].c,pTreePrint);
printf("(%c)",flag);
fputc('(',pTreePrint);
fputc(flag,pTreePrint);
fputc(')',pTreePrint);
printf("---------------------------------------------------\n");
fprintf(pTreePrint,"---------------------------------------------------\n");
blanknumber++;
HuffmanTreeReverse(HT,HT[posision].lchild,blanknumber,pTreePrint,'L');
HuffmanTreeReverse(HT,HT[posision].rchild,blanknumber,pTreePrint,'R');
}
// (4)P:印代码文件(Print)。将文件CodeFile以紧凑格式显示在终端上,每行50个代
// 码。同时将此字符形式的编码文件写入文件CodePrin中。
void Print()
{
FILE* pCodeFile=fopen("C:/tmp/CodeFile.txt","r");
FILE* pCodePrin=fopen("C:/tmp/CodePrin.txt","w");
char buf[51];
int count=0;
while(fgets(buf,51,pCodeFile)!=NULL)
{
fprintf(pCodePrin,buf);
fputc('\n',pCodePrin);
printf("%s\n",buf);
count++;
}
fseek(pCodeFile,50*count,SEEK_SET);
char c=fgetc(pCodeFile);
while(!feof(pCodeFile))
{
fputc(c,pCodePrin);
putchar(c);
c=fgetc(pCodeFile);
}
fclose(pCodeFile);
fclose(pCodePrin);
printf("打印完成。\n");
}
//(1) I:初始化(Initialization)。从终端读入字符集大小n,以及n个字符和n个权值, .
//建立哈夫曼树,并将它存于文件hfmTree中。
void Initialization()
{
HuffmanChar *hchararray;
int n;
printf("请输入待编码字符数目:\n");
scanf("%d",&n);
scanfchar(&hchararray,n);
HuffmanTree HT;
//HuffmanCode HC;//HC中字符顺序为hcharray中输入的顺序
HuffmanCoding(&HT,hchararray,n);
ntof(HT,n);//将哈夫曼树存储在文件中
system("cls");
}
void ntof(HuffmanTree HT,int n)
{
int i;
int flag=0;
FILE *phfmTree=fopen("C:/tmp/hfmTree","wb");
for(i=1;i<=2*n-1;i++)
{
if(fwrite(HT+i,sizeof(HTNode),1,phfmTree)==0)
{
printf("写入文件失败!");
flag=1;
break;
}
//fwrite(HT+i,sizeof(HTNode),1,phfmTree);
}
if(flag==0) printf("写入文件成功!");
system("pause");
fclose(phfmTree);
}
void scanfchar(HuffmanChar** hchararray,int n)
{
fflush(stdin);
printf("请依次输入待编码字符及其权值,中间用逗号间隔,并按行分隔各组:\n");
unsigned int i;
*hchararray=(HuffmanChar*)malloc(sizeof(HuffmanChar)*n);
for(i=0;i<n;i++)
{
(*hchararray)[i].c=getchar();
getchar();//清理逗号
scanf("%d",&(*hchararray)[i].weight);
getchar();//清理回车换行号
}
}
void HuffmanCoding(HuffmanTree* HT,HuffmanChar* hchararray,int n)
{
if(n<=1) return;
int size=2*n-1;
int i;
*HT=(HuffmanTree)malloc((size+1)*sizeof(HTNode));//0号单元未用
HuffmanTree p=(*HT)+1;
for(i=1;i<=n;i++)//前n个单元存放树的叶子
{
p->c=hchararray->c;
p->weight=hchararray->weight;
p->parent=0;
p->lchild=0;
p->rchild=0;
p++;
hchararray++;
}
for(i=n+1;i<=size;i++)//后n-1个单元存放内部节点,其中最后一个节点为根节点
{
p->c='#';
p->weight=0;
p->parent=0;
p->lchild=0;
p->rchild=0;
p++;
}
int s1,s2;
for(i=n+1;i<=size;i++)//建哈夫曼树
{//在(*HT)[1...i-1]选择parent为0且weight最小的两个结点,其序号分别为s1和s2,其中s1<s2
Select((*HT),i-1,&s1,&s2);
(*HT)[s1].parent=i;
(*HT)[s2].parent=i;
(*HT)[i].lchild=s1;
(*HT)[i].rchild=s2;
(*HT)[i].weight=(*HT)[s1].weight+(*HT)[s2].weight;
}
}
void Select(HuffmanTree HT,int n,int* s1,int* s2)
{//在(*HT)[1...i-1]选择parent为0且weight最小的两个结点,其序号分别为s1和s2,其中s1<s2
int i,flag=0;
for(i=1;i<=n;i++)//求出前两个parent为零的i
{
if(HT[i].parent==0&&flag==0)
{
*s1=i;
flag=1;
}
else if(HT[i].parent==0&&flag==1)
{
*s2=i;
break;
}
}
if(HT[*s1].weight>HT[*s2].weight)
{
flag=*s1;
*s1=*s2;
*s2=flag;
}
for(i++;i<=n;i++)
{
if(HT[i].parent==0&&HT[i].weight<HT[*s1].weight)
{
*s2=*s1;
*s1=i;
}
else if(HT[i].parent==0&&HT[i].weight<HT[*s2].weight)
{
*s2=i;
}
}
}
// E:编码(Encoding)。利用已建好的哈夫曼树(如不在内存,则从文件hfmTree中
// 读入),对文件ToBeTran中的正文进行编码,然后将结果存入文件CodeFile中。
void Encoding()
{
HuffmanTree HT;
int i,n;
HuffmanCode HC;
fton(&HT,&n);
ttoa(HT,n,&HC);
FILE* pCodeFile=fopen("C:/tmp/CodeFile.txt","w");
FILE* pToBeTran=fopen("C:/tmp/ToBeTran.txt","r");
char tbt=fgetc(pToBeTran);
while(!feof(pToBeTran))
{
for(i=1;i<=n;i++)
{
if(tbt==HC[i][0])
{
fputs(HC[i]+1,pCodeFile );
break;
}
}
tbt=fgetc(pToBeTran);
}
fclose(pCodeFile);
fclose(pToBeTran);
printf("\n编码完成\n");
}
void fton(HuffmanTree* HT,int* n)
{
int i,size;
FILE* phfmTree =fopen("C:/tmp/hfmTree","rb");
//查看文件大小
fseek(phfmTree, 0, SEEK_END);
size = ftell(phfmTree)/sizeof(HTNode);
rewind(phfmTree);
//创建文件大小的存储空间
*HT=(HuffmanTree)malloc(sizeof(HTNode)*(size+1));
fread(*HT+1,sizeof(HTNode),size,phfmTree);//录入完成
fclose(phfmTree);
*n=(size+1)/2;
}
void ttoa(HuffmanTree HT,int n,HuffmanCode *HC)
{
//---从叶子到根逆向求每个字符的哈夫曼编码
int i;
char *cd=(char*)malloc(sizeof(char)*n);//为暂时存储分配字符编码的工作空间
int current,currentparent;
int currentcodeposision;
*HC=(HuffmanCode)malloc(sizeof(char*)*(n+1));//分配n个字符的头指针向量,头指针的顺序与输入顺序一致,HC用来编码
cd[n-1]='\0';//编码结束符
for(i=1;i<=n;i++)//逐个字符求哈夫曼编码
{
currentcodeposision=n-1;//编码结束符位置,其开始位置由长度而定
current=i;
currentparent=HT[i].parent;
while(currentparent!=0)//从叶子到根逆向求编码
{
if(HT[currentparent].lchild==current)
{
currentcodeposision--;
cd[currentcodeposision]='0';
}
else if(HT[currentparent].rchild==current)
{
currentcodeposision--;
cd[currentcodeposision]='1';
}
current=currentparent;
currentparent=HT[currentparent].parent;
}
currentcodeposision--;
cd[currentcodeposision]=HT[i].c;
(*HC)[i]=(char*)malloc(sizeof(char)*(n-currentcodeposision));
strcpy((*HC)[i],cd+currentcodeposision);
}
free(cd);//释放工作空间
}
// (3)D:译码(Decoding)。利用已建好的哈夫曼树将文件CodeFile中的代码进行译
// 码,结果存入文件 TextFile 中
void Decoding()
{
FILE* pCodeFile=fopen("C:/tmp/CodeFile.txt","r");
FILE* pTextFile=fopen("C:/tmp/TextFile.txt","w");
HuffmanTree HT;
int n;
fton(&HT,&n);
int size=2*n-1;
char currentchar=fgetc(pCodeFile);
int curptr=size;
while(!feof(pCodeFile))
{
if(currentchar=='0') curptr=HT[curptr].lchild;
else curptr=HT[curptr].rchild;
if(HT[curptr].lchild==0)
{
fputc(HT[curptr].c,pTextFile);
putchar(HT[curptr].c);
curptr=size;
}
currentchar=fgetc(pCodeFile);
}
fclose(pCodeFile);
fclose(pTextFile);
printf("\n译码完成\n");
}
函数调用关系图反映了演示程序的层次结构
四、 调试分析
- 出现频率高的字符用长码,概率小的用长码。由于哈夫曼树的wpl最小,这样,编码所需要的比特数最小,从而达到编码压缩的目的。
- 在编码过程中要考虑两个问题,第一,数据的最小冗余编码问题,第二,译码的唯一性问题
- 由于传送过程中没有出现分隔符,所以必须采用前缀编码,
五、 用户手册 - 用户手册操作系统为win10,执行文件为main.exe
- 用户界面
- 测试
I:录入上图的要求测试案例
Tobetran文件为:
SOME EIABHAFFMAN TEREE ELKAN EELSKDU XBEIOSN DISDJAELR
编码后Codefile文件为: 00111001110010010111010011010101000000001101011001111001111001010100111111110101000100100101110101011111000011101001111110100101011100111100001110110000011111100001010100000010011010010011011111110110011000111011011000010001010010101110010
P:打印Print:
TextFile文件为:
SOME EIABHAFFMAN TEREE ELKAN EELSKDU XBEIOSN DISDJAELR
哈夫曼树的凹型表示为:
#(M)---------------------------------------------------
#(L)---------------------------------------------------
#(L)---------------------------------------------------
#(L)---------------------------------------------------
#(L)---------------------------------------------------
C(L)---------------------------------------------------
U®---------------------------------------------------
H®---------------------------------------------------
#®---------------------------------------------------
R(L)---------------------------------------------------
S®---------------------------------------------------
#®---------------------------------------------------
E(L)---------------------------------------------------
#®---------------------------------------------------
I(L)---------------------------------------------------
N®---------------------------------------------------
#®---------------------------------------------------
#(L)---------------------------------------------------
#(L)---------------------------------------------------
#(L)---------------------------------------------------
#(L)---------------------------------------------------
B(L)---------------------------------------------------
G®---------------------------------------------------
#®---------------------------------------------------
P(L)---------------------------------------------------
Y®---------------------------------------------------
O®---------------------------------------------------
#®---------------------------------------------------
A(L)---------------------------------------------------
#®---------------------------------------------------
D(L)---------------------------------------------------
L®---------------------------------------------------
#®---------------------------------------------------
#(L)---------------------------------------------------
#(L)---------------------------------------------------
#(L)---------------------------------------------------
#(L)---------------------------------------------------
V(L)---------------------------------------------------
#®---------------------------------------------------
#(L)---------------------------------------------------
#(L)---------------------------------------------------
J(L)---------------------------------------------------
Q®---------------------------------------------------
#®---------------------------------------------------
X(L)---------------------------------------------------
Z®---------------------------------------------------
K®---------------------------------------------------
W®---------------------------------------------------
#®---------------------------------------------------
M(L)---------------------------------------------------
F®---------------------------------------------------
T®---------------------------------------------------
®---------------------------------------------------
六、 附录