【实验目的】
利用哈夫曼编码进行通信可以大大提高信道利用率,缩短信息传输时间,降低传输成本。但是,这要求在发送端通过一个编码系统对待传数据预先编码,在接收端将传来的数据进行译码(解码)。对于双工信道(即可以双向传输信息的信道),每端都需要一个完整的编/译码系统。试为这样的信息收发站设计一个哈夫曼编译码系统。
【实验要求】
(1)从数据文件DataFile.dat中读入字符及每个字符的权值,建立哈夫曼树HuffTree;
(2)编码(EnCoding)。用已建好的哈夫曼树,对文件ToBeTran.dat中的文本进行编码形成报文,将报文写在文件Code.txt中;
(3)译码(Decoding)。利用已建好的哈夫曼树,对文件CodeFile.dat中的代码进行解码形成原文,结果存入文件Textfile.txt中;
(4)输出(Output):输出ToBeTran.dat及其报文Code.txt;输出CodeFile.dat及其原文Textfile.txt;
[备注]:
(1)ToBeTran.dat是原文文件,为了使输出的哈夫曼树不太大,规定原文中只能出现A-H的8个字符,当然对程序稍加修改就可以对出现的所有可输和字符进行处理。
(2数据文件DataFile.dat中,元素类型为(字符,权值) , DataFile.dat的建立可以根据ToBeTran.dat原文文件,通过统计出现的字符及各字符出现的次数(与出现的概率作用相同)而得到。
(3)CodeFile.dat是报文文件.
(4)原文文件、报文文件、数据文件也可能是文本文件ToBeTran.txt、CodeFile.txt、DataFile.txt
【主要功能】
(1)创建数据文件DataFile.dat
(2)创建原文文件ToBeTran.dat
(3)创建报文文件CodeFile.dat
(4)读取数据文件DataFile.dat建立哈夫曼树HuffTree
(5)读取文本文件DataFile.txt建立哈夫曼树HuffTree
(6)对报文文件CodeFile.dat解码
(7)对报文文件CodeFile.txt解码
(8)打印字符集的哈夫曼编码
(9)打印指定字符的哈夫曼编码
(10打印译码结果。
(11)统计数据文件长度
(12)统计源文件长度
(13)统计报文文件长度
(14)菜单显示
(15)获得系统时间,记录日志时使用
(16)将操作日志保存到文件Log.dat中
【参考程序】
huftree.h
/*
Huffman树的基本操作
*/
#include "stdio.h"
#include "malloc.h"
#include "string.h"
#define MAXBIT 30
#define MAXVALUE 32767
#define MAXCOUNT 30
typedef char DataType;
typedef struct node{
DataType data;
int weight; //权重
int parent;// 双亲
int left;//左孩子
int right;//右孩子
}HufNode;
typedef struct{//对哈夫曼树进行数据编码
char code[MAXBIT];
int start;
}HufCode;
typedef struct
{
char ch;
int w;
}DFileNode;
void creatDataFile()
{
FILE *fp;//数据文件
FILE *fpToBeTran;//源文件
char c;
DFileNode s;//建立一个DFileNode结构体方便存入叶子节点和其权重
int i, a[27],len1, len2;
for (i = 1; i <= 26; i++) a[i] = 0;//先将准备纯如数据权重数组初始化都设为0次
printf("******************************************************************\n");
printf("------------创建母本文件ToBeTran.txt和数据文件TextFile.txt------\n");
printf("------------ 请输入一段大写字母,以#结束 -------\n");
printf("******************************************************************\n");
c = getchar();//输入数据
fpToBeTran = fopen("ToBeTran.txt", "w");//打开数据文件
while (c != '#')//在#之前将所有数据按照输入顺序输入到源文件中
{
fputc(c, fpToBeTran);
a[c - 64]++;//按照ABCD...顺序记录相同字母出现的个数(即权重)没出现的将维持原值为0
c = getchar();
}
fclose(fpToBeTran);
fp = fopen("TextFile.txt", "w");//打开数据文件准备写入
for (i = 1; i <= 26; i++)//将26个大写字母依次填入DFileNode s结构体中
{
s.ch = i + 64;//依次在s.ch存入26个大写字母
s.w = a[i];//将数组中的权重数据存入s.w里面
if(s.w != 0)//只有权重不为零(即输出过的数据权重不为零存入到数据文件中)
{
fwrite(&s, 1, sizeof(DFileNode), fp);
}
}
fclose(fp);
fp = fopen("TextFile.txt", "r");
printf("字符及每个字符出现的次数\n");
while (fread(&s, 1, sizeof(DFileNode), fp))
{
printf("(%c ,%d), ", s.ch, s.w);
}
printf("\n");
/*统计数据文件的长度*/
rewind(fp);//将文件指针指向文件开头
fseek(fp, 0, SEEK_END);//将文件指针指向文件的结尾;
len1 = ftell(fp);//获取当前文件指针在文件内的位置,单位为byte
printf("数据文件长度:%d Byte\n", len1);//len为文件的长度。
/*统计源文件的长度*/
fpToBeTran = fopen("ToBeTran.txt", "r");
rewind(fpToBeTran);//将文件指针指向文件开头
fseek(fpToBeTran, 0, SEEK_END);//将文件指针指向文件的结尾;
len2 = ftell(fpToBeTran);//获取当前文件指针在文件内的位置,单位为byte
printf("源文件长度:%d Byte\n", len2);//len为文件的长度。
fclose(fpToBeTran);
fclose(fp);
}
/**
* @brief 哈夫曼建树
* @param weight:权重 ch:码本 hf:结点
* @retval 空
*/
void HuffmanTree(int weight[],char ch[],int n,HufNode hf[])
{
int i,j;
int mw1,mw2; //存储权值最小和第二小的权值
int node1,node2; //存储数组下标
for(i=0;i<2*n-1;i++) //初始化哈夫曼树 对于有n个叶子结点的哈夫曼树,应该有2n-1个结点
{ //前n个结点为叶子结点
hf[i].data=ch[i];//读取ch数组数据写入hf[].data中
if(i<n) //先将读取的权重数据依次按照0.1.2.3.4......(小于等于n-1个叶子节点)安放到hf[i].weight中
hf[i].weight=weight[i]; //叶子结点赋值初始权值(未进行排序)
else
hf[i].weight=0; //将分支结点初始化,将权值部分赋0;
hf[i].parent=-1; //先将所有节点的双亲初始值设为 - 1,左孩子和右孩子初始值都设为 - 1;
hf[i].left=-1;
hf[i].right=-1;
}
//将文件里面的内容存进树中(此刻没有进行正式建树)
for(i=n;i<2*n-1;i++) //后n-1个结点为分支结点
{
mw1=mw2=MAXVALUE; //给最小和次小的树赋最大值
node1=node2=-1; //将数组下标设为-1
for(j=0;j<=i-1;j++) //找出的两颗权值最小的子树
{
if(hf[j].parent==-1) //找到双亲域为-1的叶子结点 (-1的就是说明它是独立的,未进行排序)下次寻找最小和次小时直接排除在外不参与排序
{
if(hf[j].weight<mw1)
{ //每次找出剩下的最小权值,并将最小权值赋给次小
mw2=mw1; //为了保留次小值权值
node2=node1; //为了保留次小值下标
mw1=hf[j].weight; //mw1存当前已排最小权值,为下次循环排序做准备
node1=j; //保留当前已排数据最小值的下标
}
else if(hf[j].weight<mw2) //新的权值和看看后面后面是否还有比第二小还小的值纯在
{
mw2=hf[j].weight; //对比当前排序的权值看是否比次小值还小,则令其为新的次小值
node2=j;//标记其下标
}
}
}
//用node1和node2构造一棵新的二叉树,二叉树根的权值为所排中最小权值node1下标和node2下标所代表的权值和
hf[i].weight=hf[node1].weight+hf[node2].weight;
hf[node1].parent=i; //node1的双亲为新构造二叉树的根
hf[node2].parent=i; //node2的双亲为新构造二叉树的根
//填充根的左右孩子指针域,使之分别指向node1和node2
hf[i].left=node1;
hf[i].right=node2;
}
printf("哈夫曼树建立完成\n");
}
/*哈夫曼树的编码*/
void HuffmanCode(HufNode hf[],HufCode hfc[],int n)//编码过程 从子到根,其中n为所要编码的叶子节点个数
{
int i,parent,left;
HufCode hc;
for(i=0;i<n;i++)//对n个字符进行编码 计算每个叶子节点的哈夫曼编码
{
hc.start=n; //树的高度一定小于等于树的叶子节点数(即编码长度一定小于n)
left=i; //将树中的第一个字符的下标给left暂存
parent=hf[i].parent; //找到i下标字符的双亲(是地址)给parent暂存
while(parent!=-1) //没有到达根结点,则编码继续
{
if(hf[parent].left==left) //左孩子编码(判定此结点是否为其左孩子)
hc.code[hc.start--]='0';//方便输出,并且方便记录最后一个编码字符start+1的位置
else //右孩子编码
hc.code[hc.start--]='1';
left=parent;//移位到其双亲结点
parent=hf[left].parent; //向根进发
}
hc.start++; //让译码的时候从最后一个编码的下标开始
hfc[i] = hc; //获得一个编码组合(其为所有叶子节点编码组成的数组)
}
}
/**
* @brief 哈夫曼译码
* @param hfmTree:结点 n:结点个数 Translated:译码过的文本
* @retval 空
*/
void HuffmanDecode(HufNode hfmTree[],char *Translated, int n)//译码过程
{
int i, j = 0;
int dex = 0;
FILE *fptranslated, *fpcode;
char ch[100];
fptranslated = fopen("Translated.txt", "w");
fpcode = fopen("Code.txt", "r");
if (fptranslated == NULL || fpcode == NULL)
{
printf("文件创建失败!\n");
}
fgets(ch,100,fpcode); //读取文本中的编码数据从code.txt文件中提取出写入到ch数组中
i = 2 * n - 2; //根结点为2*N-2,即根节点所在的数组的下标
printf("\n译码结果为:");
while (ch[j] != NULL)//防止其为空
{
if (ch[j] == '0')//如果为'0',则指向其左孩子
i = hfmTree[i].left;
else if (ch[j] == '1')//如果为'1',则指向其右孩子
i = hfmTree[i].right;
if (hfmTree[i].left == -1) //从根结点一直找到叶子(叶子节点的孩子都为-1)
{
//直到碰到叶子节点
printf("%c", hfmTree[i].data);
Translated[dex++] = hfmTree[i].data;//将所需译码后的字母填入Translated
fprintf(fptranslated, "%c", hfmTree[i].data);//将所要所译码后的数据写入Translated.txt
i = 2 * n - 2; //译完一段编码后置为头结点继续翻译
}
j++;//下一个所需译码数据的第一个码值的数组下标位置
}
printf("\n");
Translated[dex] = '\0';//结束输入
fclose(fptranslated);
fclose(fpcode);
}
huftree.cpp
/* -----------------------------------Includes -------------------------------------------*/
#include "huftree.h"
#include <conio.h>
#include <stdlib.h>
#include "string.h"
/* -----------------------------------Includes -------------------------------------------*/
/* ---------------------------------- 函数的声明 -----------------------------------------*/
int Files_open(int n);
int Huffman_Code(HufNode hf[],HufCode hfc[],int n);
void Huffman_DisCode(HufNode hf[],HufCode hfc[],int n);
void CompareTxt(char *ToBeTran, char *TextFile);
void HMI_display(void);
char ToBeTran[100];
char TextFile[30];
int weight[30];
/* ---------------------------------- 函数的声明 -----------------------------------------*/
int main(int argc, char* argv[])
{
FILE *fpin_TextFile;
FILE *fplog; //对人员操作的时间进行记录保存的文件
DFileNode s;
int command;
int i = 0;
char translated[100]; //译码的字符串
int Numofcode;
int flag[5]={0};
creatDataFile();//输入源文件数据,并将数据存入数据文件中,并统计文件长度
HMI_display();
fpin_TextFile = fopen("TextFile.txt", "r");//打开含有输入字母和字母次数的文件(其中为所有输入的字母)
fplog = fopen("log.txt", "a");//打开log文件(“a”打开或新建一个文本文件,只允许在文件末尾追写)即只能增加不能删除
if (fpin_TextFile == NULL || fplog == NULL)//文件不存在打开失败(日志文件和所需读取数据的母文件)
{
printf("TextFile.txt打开失败!\n");
return 1;
}
while (fread(&s, 1, sizeof(DFileNode), fpin_TextFile))
{
TextFile[i] = s.ch;
i++;
}
fclose(fpin_TextFile);
Numofcode = i;//得到字符串的长度
HufNode *HufTree = (HufNode*)malloc(sizeof(HufNode)*(2*Numofcode+1));//申请内存空间(申请HufNode类型的内存空间)预先申请可能所有节点所需的空间
HufCode *HufCodes = (HufCode*)malloc(sizeof(HufCode)*Numofcode);//申请需要哈夫曼编码的空间
while (1)
{
do
{
printf("=============================================\n");
printf("\n请输入指令(0-5):\n");
scanf("%d", &command);
} while (command < 0 || command > 5);//只允许输入0到4的数字操作码
if (command == 5)
{
printf("欢迎下次使用哈夫曼系统!");
return 0;
}
switch(command)//选择语句
{
case 0://0选项是输出操作时的系统时间
Files_open(Numofcode); //文件打开(储存哈夫曼编码的空间)
fprintf(fplog, "【%s】 %s %s\t\n",__DATE__,__TIME__,"文件打开");//日志文件输出
printf("【%s】 %s %s\t\n", __DATE__, __TIME__, "文件打开");
flag[0] = 1;//标志位,为接下来运行提供标志(防止未初始化就行编码)
break;//跳出当前case选择函数
case 1:
if (flag[0] != 1) printf("您还没有进行初始化,请初始化后再试试!"); //错误操作警告
else
{
HuffmanTree(weight,TextFile,Numofcode,HufTree); //建立哈夫曼树(weight权重文件,TextFile读取的文本,Numofcode叶子节点的个数,HufTree树
fprintf(fplog, "【%s】 %s %s\t\n",__DATE__,__TIME__,"哈夫曼树建立");//日志文件输出
printf("【%s】 %s %s\t\n", __DATE__, __TIME__, "哈夫曼树建立");
flag[1] = 1;//对下一个环节设定标志位
}
break;
case 2:
if (flag[0] != 1) printf("您还没有进行初始化,请初始化后再试试!"); //错误操作警告
else if (flag[1] != 1) printf("您还没有进行建树操作,请建树后再试试!");
else
{
Huffman_Code(HufTree,HufCodes,Numofcode); //哈夫曼编码(哈夫曼树HufTree的每个节点的数据,储存编码的(数组)HufCode结构HufCodes,Numofcode叶子节点数)
fprintf(fplog, "【%s】 %s %s\t\n",__DATE__,__TIME__,"哈夫曼编码");
flag[2] = 1;//对下一个环节设定标志位
}
break;
case 3:
if (flag[0] != 1) printf("您还没有进行初始化,请初始化后再试试!"); //错误操作警告
else if (flag[1] != 1) printf("您还没有进行建树操作,请建树后再试试!");
else if (flag[2] != 1) printf("您还没有进行哈夫曼编码操作,请编码后再试试!");
else
{
HuffmanDecode(HufTree,translated,Numofcode); //哈夫曼译码
fprintf(fplog, "【%s】 %s %s\t\n",__DATE__,__TIME__,"哈夫曼译码");//日志文件输出
printf("【%s】 %s %s\t\n", __DATE__, __TIME__, "哈夫曼译码");
fclose(fplog);//关闭储存操作的时间日志文件
flag[3] = 1;//标志位,为下一个环节提供标志
}
break;;
case 4:
CompareTxt(ToBeTran, translated); //比较译码前后是否一致
break;
default:
break;//其他未知的直接结束
}
}
while (1);
return 0;
}
/*
* @brief 文件的打开
* @param n:从文件循环读入的个数
* @retval 返回文件是否打开成功
*/
int Files_open(int n)
{
DFileNode s;//用户操作指令(0.1.2.3.4)
FILE *fpin_weight;
FILE *fpin_TobeTran;
int i = 0, j;
fpin_weight = fopen("TextFile.txt", "r");//打开文件(数据的权重文件)
fpin_TobeTran = fopen("ToBeTran.txt", "r");//打开原文件
if (fpin_weight == NULL)//文件不存在则打开失败
{
printf("TextFile.txt文件打开失败!\n");
return 1;
}
if (fpin_TobeTran == NULL)//文件不存在则打开失败
{
printf("ToBeTran.txt文件打开失败!\n");
return 1;
}
while (fread(&s, 1, sizeof(DFileNode), fpin_weight ))//依次将文本的权重数据复制到数组里
{
weight[i] = s.w;//将权重读入到权重weight数组中
i++;
}
fgets(ToBeTran, 100, fpin_TobeTran);//从文本读入待转换的数据储存到数组ToBeTran中
printf("文件读取成功\n");
printf("您需要编码的文本信息是:%s\n", ToBeTran);
fclose(fpin_weight);//关闭文件
fclose(fpin_TobeTran);
}
/*
* @brief 哈夫曼编码
* @param hf:结点 hfc:结点编码 n:结点个数
* @retval 返回文件是否打开成功
*/
int Huffman_Code(HufNode hf[],HufCode hfc[],int n)
{
int i, j, m, len3;
char str[100], c;
FILE *fpCodeout;
fpCodeout = fopen("Code.txt", "w"); //打开编码二进制文件
if (fpCodeout == NULL)
{
printf("文件打开失败!\n");
return 1;
}
HuffmanCode(hf,hfc,n);//哈夫曼编码hf哈夫曼树,hfc编储存结构(以数组的方式),n叶子节点的个数
printf("以下是哈夫曼编码结果对应表:\n");
printf("\n*********Huffman Code*********\n");
for(i=0;i<n;i++) //输出哈夫曼编码对应表
{
printf("%c(%d) :\t",TextFile[i],hf[i].weight);
for(j=hfc[i].start;j<=n;j++)//从根节点开时进行输出
{
printf("%c",hfc[i].code[j]);//显示编码结果
}
printf("\n");
}
printf("\n");
printf("\n进行编码的字符为:");
puts(ToBeTran);//将存入数组的需要编码的源文件展示出来
printf("该字符串编码为:\n");
for (i = 0; ToBeTran[i] != NULL; i++)//依次输出源文件存入数据的数组
{
for(m = 0; m < n; m++)//依次输出叶子节点字符与源文件读出来存入数组的数据依次作比较。
{
if (ToBeTran[i] == TextFile[m])//若该字符与ToBeTran数组的字符相同
{
for (j = hfc[m].start; j <= n; j++)//依次输出该字符的编码
{
printf("%c", hfc[m].code[j]); //输出编码的结果
fprintf(fpCodeout, "%c", hfc[m].code[j]);//将编码结果写入code.txt文件中
}
}
}
}
printf("\n");
/*统计报文文件的长度*/
rewind(fpCodeout);//将文件指针指向文件开头
fseek(fpCodeout, 0, SEEK_END);//将文件指针指向文件的结尾;
len3 = ftell(fpCodeout);//获取当前文件指针在文件内的位置,单位为byte
printf("报文文件长度:%d Byte\n", len3);//len为文件的长度。
printf("\n编码成功!!!\n\n");
fclose(fpCodeout);
}
/*
* @brief 译码前后的比较
* @param ToBeTran:
* @retval 无
*/
void CompareTxt(char *ToBeTran, char *translated)
{
if (strcmp(ToBeTran, translated) == 0)
printf("\n比较结果一致!\n");
else
printf("\n比较结果不一致!\n");
}
/*
* @brief 人机界面
* @param 无
* @retval 无
*/
void HMI_display(void) //人机界面
{
printf("\n\n\n\n\n\n\n");
printf("****************************************\n");
printf("——————哈夫曼编码小程序——————\n");
printf("****************************************\n");
printf("请按任意键进行程序...");
getch(); //任意键继续
system("cls");//清屏
printf("**************************\n");
printf("Welcome to HuffmanCode!\n");
printf("0.初始化\n");
printf("1.建立哈夫曼树\n");
printf("2.哈夫曼编码\n");
printf("3.哈夫曼译码\n");
printf("4.比较译码前后\n");
printf("**************************\n");
}
图
不好意思,走错片场了!!!
效果图自行运行后查看