哈夫曼树的文件操作
1. 问题描述:
利用哈夫曼编码进行通信可以大大提高信道利用率,缩短信息传输时间,降低传输成本。但是,这要求在发送端通过一个编码系统对待传数据预先编码,在接收端将传来的数据进行译码(解码)。对于双工信道(即可以双向传输信息的信道),每端都需要一个完整的编/译码系统。试为这样的信息收发站设计一个哈夫曼编/译码系统。
2.一个完整的系统应具有以下功能:
1)初始化(Initialzation)。从数据文件 DataFile.data 中读入字符及每个字符的权值,建立哈夫曼树 HuffTree;
2)编码(EnCoding)。用已建好的哈夫曼树,对文件 ToBeTran.data 中的文本进行编码形成报文,将报文写在文件 Code.txt 中;
3)译码(Decoding)。利用已建好的哈夫曼树,对文件 CodeFile.data 中的代码进行解码形成原文,结果存入文件 Textfile.txt 中;
4)输出(Output): 输出ToBeTran.data 及其报文 Code.txt;输出 CodeFile.data 及其原文 Textfile.txt;
要求:所设计的系 统应能在程 序执行的过程中,根据 实际情况(不同的输入 )建立DataFile.data、ToBeTran.data 和 CodeFile.data 三个文件,以保证系统的通用性。
编程思想:
1)根据哈夫曼树的定义:有n个叶子节点的哈夫曼树必有m = 2 * n - 1个节点。
构造哈夫曼树时:将前n个节点初始化相应的字符和权值,从中找到两个父母节点为0(意味着未被访问过)且权值最小的两个数作为第n+1个节点的左右孩子,依次初始化剩余m-n个节点。
2)根据哈夫曼树的定义:有n个叶子节点的哈夫曼树其深度不会超过n-1。
编码时:1.定义临时存放哈夫曼编码的数组cd[],从叶到根逆向求编码,将得到的数据倒着存入数组cd[]中,再将数组cd拷贝到HC中,2.再找下一个叶节点,将得到的数据再倒着存入数组cd[]中,再将数组cd拷贝到HC中。重复2,最后不要忘了释放cd[]的空间。
3)译码时:依据编码向下遍历树,直到向下碰到叶子节点。
4)输出文件内容时:用一个数组保存文件名。
注意!!!
-
打开文件时文件的路径(本程序将文件放在程序运行的同一目录下)
-
输出文件内容时,输入的文件名要带后缀名。
-
为便与更好的使用可利用for循环和控制输出语句将哈夫曼树和每个字母对应的哈夫曼编码输出。(本程序未写)。
运行:
-
假设建立的哈夫曼树:
字符分别为a s d f
权值分别为1 2 3 4 -
在文件DataFile.data中写下1a2s3d4f#(以#作为结束标志)
在文件ToBeTran.data中写下asdf#(以#作为结束标志)
在文件CodeFile.data中写下110111100#(以#作为结束标志) -
运行功能一
会建立如图所示的哈夫曼树:
-
运行功能二
会生成相应的哈夫曼编码a–110,s–111,d–10,f–0
会生成文件Code.txt,内容为110111100# -
运行功能三
会生成文件Textfile.txt,内容为asdf#
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#define N 100
#define ERROR 0
#define OK 1
typedef struct {
int weight;
char ch;
int parent,lchild,rchild;
}HTNode,*HuffmanTree; //动态分配数组存储哈夫曼树
typedef char **HuffmanCode; //用于存储哈夫曼编码的数组
void Select(HuffmanTree &HT,int i,int &s1,int &s2)
{//找哈夫曼树中权值最小的两个节点
int temp,j,k;
//找权值最小节点
for(j = 1;j <= i;j++)//找父母为0的节点temp
if(HT[j].parent == 0)
{
temp = j;
break;
}
for(j = 1;j <= i;j++)
if (HT[j].parent == 0)
if (HT[j].weight < HT[temp].weight) //设temp为最小节点
temp=j;//如果比temp小,则交换
s1 = temp;
//找权值次小节点
for(k = 1;k <= i;k++)//再找父母为0的节点temp
if(HT[k].parent == 0 && k != s1)
{
temp = k;
break;
}
for(k = 1;k <= i;k++)
if (HT[k].parent == 0 && k != s1) //找不与s1相等的最小节点
if (HT[k].weight < HT[temp].weight)
temp=k;
s2 = temp;
}
void Initialzation (HuffmanTree &HT,int n)
{ //n个权值,构造哈夫曼树HT
FILE *fp;
int m,i,s1=1,s2=1;
if ((fp = fopen("DataFile.txt", "r")) == NULL)
{
printf("文件读取失败\n");
exit(0);
}
if(n < 1)
return ;
m = 2 * n - 1;
HT = (HuffmanTree)malloc((m + 1)*sizeof(HTNode));
for(i = 1;i <= m;i++) //0号不用,进行初始化
HT[i].parent = HT[i].lchild = HT[i].rchild = 0;
for(i = 1;i <= n;i++) //建n棵仅含根结点的二叉树的森林
fscanf(fp, "%d%c", &HT[i].weight,&HT[i].ch);
fclose(fp);
for( i = n + 1;i <= m;i++) //建哈夫曼树
{ //在HT[1..i-1]中选择parent为0且weight最小的两个结点,其序号分别为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 EnCoding (HuffmanTree HT,HuffmanCode &HC,int n)
{ //从叶子到根逆向求每个字符的哈夫曼编码
FILE *fp, *fp1;
char *cd;
int i,start,c,f;
HC=(HuffmanCode)malloc((n+1)*sizeof(char*)); //分配n个字符编码的头指针
cd =(char*)malloc(n*sizeof(char)); //分配求编码的工作空间
cd[n-1]='\0'; //编码的结束标志
for(i=1;i<=n;i++) //逐个字符求哈夫曼编码
{
start=n-1; //编码结束符位置
c=i;
f=HT[i].parent; //从叶到根逆向求编码
while( f!=0)
{
if(HT[f].lchild==c)
cd[--start]='0';
else
cd[--start]='1';
c=f;
f= HT[f].parent;
}
HC[i]=(char*) malloc((n-start)*sizeof(char));//为第i个字符编码分配空间
strcpy(HC[i],&cd[start]); //从cd复制编码串到HC[i]中
}
free(cd); //释放工作空间
if ((fp = fopen("ToBeTran.txt", "r")) == NULL) {
printf("文件错误\n");
exit(0);
}
if ((fp1 = fopen("Code.txt", "w")) == NULL)
{
printf("文件错误\n");
exit(0);
}
c = fgetc(fp);
while(c != '#')
{
for(i = 1;i <= n;i++) //建n棵仅含根结点的二叉树的森林
{
if(c == HT[i].ch) //找到字母的编号
fprintf(fp1, "%s", HC[i]);//将对应的哈夫曼编码写入文件中
}
c = fgetc(fp);
}
fprintf(fp1,"#");
fclose(fp);
fclose(fp1);
}
void Decoding(HuffmanTree HT, HuffmanCode HC,int n)
{//译码
int i;
char b;
FILE *fp,*fp1;
if ((fp = fopen("CodeFile.txt", "r")) == NULL) {//打开文件只读
printf("文件错误\n");
exit(0);
}
if ((fp1 = fopen("TextFile.txt", "w")) == NULL) {//打开文件只写
printf("文件错误\n");
exit(0);
}
b = fgetc(fp); //从文件中一个一个读取字符
i = 2 * n - 1;
while (b != '#')
{
if (b == '0')//如果为'0',则指向其左孩子
i = HT[i].lchild;
else if (b == '1')//如果为'1',则指向其左孩子
i = HT[i].rchild;
if (HT[i].lchild == 0)
{//直到碰到叶子节点
fprintf(fp1, "%c", HT[i].ch);
i = 2 * n - 1;
}
b = fgetc(fp);//再读下一个字符
}
fprintf(fp1,"#"); //将‘#’写入文件
fclose(fp);//关闭文件
fclose(fp1);
}
void Print(char *filename)
{
FILE *fp;
if ((fp = fopen(filename, "r")) == NULL) {
printf("文件错误\n");
exit(0);
}
char b = fgetc(fp);
while (b != '#')
{
printf("%c ",b);
b = fgetc(fp);
}
fclose(fp);
}
int main()
{
int a,k,n;
char filename[N];//用于保存文件名
HuffmanTree HT;
HuffmanCode HC;
printf("输入文件字母数:\n");
scanf("%d",&n);
for(k = 0;;k++)
{
printf("\n1.从文件DataFile中读取建立哈夫曼树\n");
printf("2.对文件ToBeTran编码再写入Code中\n");
printf("3.对文件CodeFile译码再写入Textfile中\n");
printf("4.输出文件内容\n");
printf("5.退出程序\n");
printf("输入你的选择:\n");
scanf("%d",&a);
switch (a)
{
case 1:
Initialzation(HT,n);
break;
case 2:
EnCoding(HT,HC,n);
break;
case 3:
Decoding(HT,HC,n);
break;
case 4:
printf("请输入文件名(带后缀名):\n");
scanf("%s",filename);
Print(filename);
break;
case 5:exit(0);
}
}
return OK;
}