哈夫曼(Huffman)编/译码器(限1人完成)
【问题描述】
利用哈夫曼编码进行通信可以大大提高信道利用率,缩短信息传输时间,降低传输成本。但是,这要求在发送端通过一个编码系统对待传数据预先编码,在接收端将传来的数据进行译码(复原)。对于双工信道(即可以双向传输信息的信道),每端都需要一个完整的编/译码系统。试为这样的信息收发站写一个哈夫曼码的编/译码系统。首先输入一段包含27个字符(包含空格)的文字(可以存在一个文件中),然后统计出各个字符出现的次数,以每个字符出现的次数为权值构造哈夫曼树,求得哈夫曼编码。
【基本要求】
一个完整的系统应具有以下功能:
1、 O: 输入一段字符(要包含27各字符)存入文件chartexfile中,统计出各个字符出现的次数并以表格的形式存入文件charsumfile中.
例如如下表:
字符 空格 A B C D E F G H I J K L M
频度 186 64 13 22 32 103 21 15 47 57 1 5 32 20
字符 N O P Q R S T U V W X Y Z
频度 57 63 15 1 48 51 80 23 8 18 1 16 1
2、I:初始化(Initialization)。从终端读入字符集大小n,n个字符及n个权值,建立哈夫曼树,并将它存于文件hfmTree中。
3、 E:编码(Encoding)。利用以建好的哈夫曼树(如不在内存,则从文件hfmTree中读入),
对文件ToBeTran中的正文进行编码,然后将结果存入文件CodeFile中。
4、D:译码(Decoding)。利用已建好的哈夫曼树将文件CodeFile中的代码进行译码,结果存入文件TextFile中。
5. P:打印代码文件(Print)。将文件CodeFile以紧凑格式显示在终端上,每行50个代码。
同时将此字符形式的编码文件写入文件CodePrin中。
4、 T:打印哈夫曼树(Tree Printing)。将已在内存中的哈夫曼树以直观的方式(树或凹入
表形式)显示在终端上,同时将此字符形式的哈夫曼树写入文件TreePrint中。
【测试数据】
用文件charsumfile给出的字符集和频度的实际统计数据建立哈夫曼树,并实现以下报文的编码和译码:“THIS PROGRAM IS MY FAVORITE”。
测试数据要求:
要求使用1、全部合法数据;2、整体非法数据;3、局部非法数据。进行程序测试,以保证程序的稳定
#include
#include
#define maxval 10000.0
#define maxsize 10000 //哈夫曼编码的最大位数
typedef struct
{
char ch;
float weight=-1;
int lchild,rchild,parent;
} hufmtree;
typedef struct
{
char bits[10000]; //位串
int start; //编码在位串中的起始位置
char ch; //字符
} codetype;
int n,m;
hufmtree tree[20000];
codetype code[10000];
void TongJi(); //统计字符个数
void huffman(hufmtree tree[]);//建立哈夫曼树
void huffmancode(codetype code[],hufmtree tree[]);//根据哈夫曼树求出哈夫曼编码
void decode(hufmtree tree[]);//依次读入电文,根据哈夫曼树译码
void print(); //打印代码文件,输出 CodeFile.txt
void TreePrinting();//打印哈夫曼树
int main()
{
while(1)
{
int t;
printf("---------------菜单-------------------\n");
printf("1.输入一段字符,统计出各个字符出现的次数.\n");
printf("2.建立哈夫曼树.\n");
printf("3.编码.\n");
printf("4.译码.\n");
printf("5.打印编码\n");
printf("6.打印哈夫曼树\n");
printf("7.退出\n");
printf("---------------------------------------\n");
printf("请选择你要进行的操作:");
scanf("%d",&t);
switch(t)
{
case 1:
TongJi();
break;
case 2:
huffman(tree);//建立哈夫曼树
printf("哈夫曼树已建立!\n");
break;
case 3:
huffmancode(code,tree);//根据哈夫曼树求出哈夫曼编码
break;
case 4:
decode(tree);//依次读入电文,根据哈夫曼树译码*/
break;
case 5:
print();
break;
case 6:
TreePrinting();
break;
case 7:
return 0;
}
}
return 0;
}
void TongJi() //统计字符个数
{
FILE *out1,*out2;
out1=fopen("chartexfile.txt","w");
out2=fopen("charsumfile.txt","w");
if(out1==NULL||out2==NULL)
{
printf("打开文件失败\n");
}
char s[1000];
int i=0;
char zf[1000]= {0}, z[1000];
int cnt=1,k,num[1000]= {0};
printf("请输入字符串(空格以-代替,按回车结束):");
scanf("%s",s);
k=strlen(s);
fprintf(out1,"%s",s);
for(i=0; i<k; i++)
{
if(zf[s[i]]==0)
{
zf[s[i]]=cnt++;
z[cnt-1]=s[i];
}
num[zf[s[i]]]++;
}
printf("字符 频率\n");
fprintf(out2,"字符 频率\n");
for(int i=1; i<cnt; i++)
{
if(z[i]!='-')
printf("%3c %4d\n",z[i],num[zf[z[i]]]);
else printf(" 空格 %4d\n",num[zf[z[i]]]);
fprintf(out2,"%3c %4d\n",z[i],num[zf[z[i]]]);
}
fclose(out1);
fclose(out2);
}
void huffman(hufmtree tree[]) //建立哈夫曼树
{
int i,j,p1,p2;//p1,p2分别记住每次合并时权值最小和次小的两个根结点的下标
float small1,small2,f;
char c;
FILE *fp,*in;
in=fopen("charsumfile.txt","r");
fp=fopen("hfmTree.txt","w");
if(fp==NULL)
{
printf("打开文件失败\n");
return ;
}
for(i=0; i<20000; i++) //初始化
{
tree[i].parent=0;
tree[i].lchild=-1;
tree[i].rchild=-1;
tree[i].weight=0.0;
}
int t;
printf("(1)根据字符频率文件 charsumfile.txt 生成哈夫曼树\n(2)手动输入字符频率生成哈夫曼树\n 请选择你要进行的操作:");
scanf("%d",&t);
if(t==2)
{
printf("请输入字符个数:");
scanf("%d",&n);
getchar();
m=2*n-1;
printf("请依次输入前 %d 个字符(中间用空格隔开,空格用‘-’代替):",n);
for(i=0; i<n; i++)
{
tree[i].ch=getchar();
getchar();
}
printf("请依次输入前 %d 个权值(中间用空格隔开)\n",n);
for(i=0; i<n; i++) //读入前n个结点的字符及权值
{
scanf("%f",&f);
tree[i].weight=f;
}
}
else if(t==1)
{
i=0;
char na[10];
fscanf(in,"%s %s",na,na);
while(EOF!=fscanf(in," %c %lf",&tree[i].ch,&tree[i].weight))
{
i++;
}
n=i;
m=n*2-1;
}
for(i=n; i<m; i++) //进行n-1次合并,产生n-1个新结点
{
p1=0;
p2=0;
small1=maxval;
small2=maxval; //maxval是float类型的最大值
for(j=0; j<i; j++) //选出两个权值最小的根结点
if(tree[j].parent==0)
if(tree[j].weight<small1)
{
small2=small1; //改变最小权、次小权及对应的位置
small1=tree[j].weight;
p2=p1;
p1=j;
}
else if(tree[j].weight<small2)
{
small2=tree[j].weight; //改变次小权及位置
p2=j;
}
tree[p1].parent=i;
tree[p2].parent=i;
tree[i].lchild=p1; //最小权根结点是新结点的左孩子
tree[i].rchild=p2; //次小权根结点是新结点的右孩子
tree[i].weight=tree[p1].weight+tree[p2].weight;
}
for(i=0; i<m; i++)
{
fprintf(fp,"%3d %3c %3.1f %3d %3d %3d\n",i+1,tree[i].ch,tree[i].weight,tree[i].parent,tree[i].lchild,tree[i].rchild);
}
fclose(fp);
fclose(in);
}//huffman
void huffmancode(codetype code[],hufmtree tree[]) //根据哈夫曼树求出哈夫曼编码
{
//codetype code[]为求出的哈夫曼编码
//hufmtree tree[]为已知的哈夫曼树
if(tree[0].weight==-1) //如不在内存,则从文件hfmTree中读入
{
printf("\n未建立哈夫曼树,已从 hfmTree.txt 中读取哈夫曼树\n");
FILE *fp;
fp=fopen("hfmTree.txt","r");
if(fp==NULL)
{
printf("打开 hfmTree.txt 文件失败!\n");
return ;
}
int i=0,T;
while(EOF!=fscanf(fp,"%d %c %f %d %d %d\n",&T,&tree[i].ch,&tree[i].weight,&tree[i].parent,&tree[i].lchild,&tree[i].rchild))
{
i++;
}
m=i;
n=(m+1)/2;
printf("%d %d\n\n",n,m);
fclose(fp);
}
int i,c,j,p;
codetype cd; //缓冲变量
for(i=0; i<n; i++)
{
cd.start=n;
cd.ch=tree[i].ch;
c=i; //从叶结点出发向上回溯
p=tree[i].parent; //tree[p]是tree[i]的双亲
while(p!=0)
{
cd.start--;
if(tree[p].lchild==c)
cd.bits[cd.start]='0'; //tree[i]是左子树,生成代码'0'
else
cd.bits[cd.start]='1'; //tree[i]是右子树,生成代码'1'
c=p;
p=tree[p].parent;
}
code[i]=cd; //第i+1个字符的编码存入code[i]
}
printf("每个字符的哈夫曼编码如下:\n");
for(i=0; i<n; i++)
{
if(code[i].ch=='-')
printf("空格: ");
else
printf("%4c: ",code[i].ch);
for(j=code[i].start; j<n; j++)
printf("%c",code[i].bits[j]);
printf("\n");
}
FILE *in,*out;
in=fopen("ToBeTran.txt","r");
out=fopen("CodeFile.txt","w");
if(in==NULL||out==NULL)
{
printf("打开文件失败\n");
return ;
}
printf("请选择你的操作 从文件 ToBeTran.txt 中读取(1)/手动输入字符(2):");
int T,cnt=0;
char s[10000];
scanf("%d",&T);
getchar();
if(T==1)
{
while(EOF!=fscanf(in,"%c",&s[cnt]))
{
if(s[cnt]==' ')
s[cnt]='-';
cnt++;
}
s[cnt]=0;
printf("字符串为:%s\n",s);
}
else if(T==2)
{
printf("请输入你要输入的字符串(空格用'-'代替):");
scanf("%s",s);
}
printf("编码为:");
for(int k=0; s[k]; k++)
{
for(int i=0; i<n; i++)
{
if(s[k]==code[i].ch)
{
for(int j=code[i].start; j<n; j++)
{
fprintf(out,"%c",code[i].bits[j]);
printf("%c",code[i].bits[j]);
}
}
}
}
printf("\n");
fclose(in);
fclose(out);
}//huffmancode
void decode(hufmtree tree[]) //依次读入电文,根据哈夫曼树译码
{
int i=0,j,t;
char b[maxsize];
char endflag='2'; //电文结束标志取2
FILE *in,*out;
in=fopen("CodeFile.txt","r");
out=fopen("TextFile.txt","w");
if(in==NULL)
{
printf("打开 CodeFile.txt 文件失败!\n");
}
printf("请选择你的操作,从CodeFile.txt读入电文(1)/手动输入电文(2):");
scanf("%d",&t);
getchar();
if(t==1)
{
printf("编码为:");
i=0;
while(EOF!=fscanf(in,"%c",&b[i]))
{
printf("%c",b[i]);
i++;
}
b[i]='2';
printf("%c\n\n",b[i]);
}
else if(t==2)
{
printf("请输入电文以 ‘2’ 结束:");
scanf("%s",b);
}
else
{
printf("输入错误!请重新输入\n");
decode(tree);
return ;
}
i=m-1; //从根结点开始往下搜索
j=0;
printf("译码后的字符为");
while(b[j]!='2')
{
if(b[j]=='0')
i=tree[i].lchild; //走向左孩子
else
i=tree[i].rchild; //走向右孩子
if(tree[i].lchild==-1) //tree[i]是叶结点
{
printf("%c",tree[i].ch);
fprintf(out,"%c",tree[i].ch);
i=m-1; //回到根结点
}
j++;
}
printf("\n");
if(tree[i].lchild!=-1&&b[j]!='2') //电文读完,但尚未到叶子结点
printf("\nERROR\n"); //输入电文有错
fclose(in);
fclose(out);
}//decode
void print() //输出 Codefile.txt
{
FILE *in,*out;
char ch;
int cnt=0;
in=fopen("CodeFile.txt","r");
out=fopen("CodePrin.txt","w");
if(in==NULL||out==NULL)
{
printf("打开文件失败\n");
return ;
}
while(EOF!=fscanf(in,"%c",&ch))
{
if(++cnt%50==0)
{
printf("\n");
fprintf(out,"\n");
}
printf("%c",ch);
fprintf(out,"%c",ch);
}
printf("\n");
fprintf(out,"\n");
fclose(in);
fclose(out);
}
void TreePrinting() //打印哈夫曼树
{
FILE *in,*out;
int num;
in=fopen("hfmTree.txt","r");
out=fopen("TreePrint.txt","w");
if(in==NULL||out==NULL)
{
printf("打开文件失败\n");
return ;
}
hufmtree a;
printf("|--------------------------------------------|\n");
fprintf(out,"|--------------------------------------------|\n");
printf("|节点 字符 | 权值 | 父节点 | 左孩子 | 右孩子 |\n");
fprintf(out,"|节点 字符 | 权值 | 父节点 | 左孩子 | 右孩子 |\n");
while(EOF!=fscanf(in,"%d %c %f %d %d %d\n",&num,&a.ch,&a.weight,&a.parent,&a.lchild,&a.rchild))
{
printf("|%4d %3c %7.1f %7d %8d %7d |\n",num,a.ch,a.weight,a.parent,a.lchild,a.rchild);
fprintf(out,"|%4d %3c %7.1f %7d %8d %7d |\n",num,a.ch,a.weight,a.parent,a.lchild,a.rchild);
}
printf("|--------------------------------------------|\n");
fprintf(out,"|--------------------------------------------|\n");
fclose(in);
fclose(out);
}