目录
前言
上一篇文章我们进行文章的Huffman码的文件压缩。那么根据Huffman码的原理,我们自然也能写出一段解压缩的代码,功能变得实用起来了!
题目
问题描述
编写一程序实现一个利用Huffman编码对一个文件进行压缩和解压的工具hzip.exe。
Huffman压缩文件原理如下:
1. 对正文文件中字符按出现次数(即频率)进行统计
2. 依据字符频率生成相应的Huffman树(未出现的字符不生成)
3. 依据Huffman树生成相应字符的Huffman编码
4. 依据字符Huffman编码压缩文件(即按照Huffman编码依次输出源文件字符)。
说明:
1. 只对文件中出现的字符生成Huffman码。
2. 采用ASCII码值为0的字符作为压缩文件的结束符(即可将其出现次数设为1来参与编码).
3. 在生成Huffman树时,初始在对字符频率权重进行(由小至大)排序时,频率相同的字符ASCII编码值小的在前;新生成的权重节点插入到有序权重序列中时,出现相同权重时,插入到其后(采用稳定排序)。
4. 遍历Huffman树生成字符Huffman码时,左边为0右边为1。
5. 源文件是文本文件,字符采用ASCII编码,每个字符占8位;而采用Huffman编码后,最后输出时需要使用C语言中的位运算将字符Huffman码依次输出到每个字节中。
输入形式
基于Huffman的文件压缩解压工具hzip.exe命令行使用形式如下:
hzip [-u] <filename.xxx>
(注:xxx是文件扩展名)
当-u参数缺省时,对当前目录下文本文件<filename.txt>采用Huffman编码方式将文件压缩到文件<filename.hzip>中,被压缩的文件必须以.txt为扩展名的文本文件,生成的压缩文件名同文本文件,但扩展名为.hzip。例如在命令行执行如下命令:
>hzip myfile.txt
采用Huffman编码方式压缩文件myfile.txt,并将压缩后结果存到文件myfile.hzip中。
当命令行有-u参数时,对当前目录下压缩文件<filename.hzip>进行解压,被解压的文件必须以.hzip为扩展名的文件,解压后的文本文件名同压缩文件,但扩展名为.txt。例如在命令行执行如下命令:
>hzip -u myfile.hzip
myfile.hzip必须是用hzip.exe压缩工具生成的压缩文件,解压后文件名为myfile.txt。
命令hzip.exe应具有一定的对命令行参数错误处理能力,如参数个数不对、参数格式不正确等。下面为命令错误使用及提示信息(错误提示信息显示在屏幕上):
>hzip
Usage: hzip.exe [-u] <filename>
>hzip srcfile.txt objfile.hzip
Usage: hzip.exe [-u] <filename>
>hzip -h srcfile.txt
Usage: hzip.exe [-u] <filename>
>hzip -u myfile.c
File extension error!
>hzip myfile.c
File extension error!
输出形式
hzip.exe工具压缩后<filename.hzip>文件格式如下:压缩文件由2部分组成,第一部分为ASCII码与Huffman码对照码表,第2部分为以Huffman编码形式的压缩后的文件。如下图所示。ASCII码与huffman码对照码表格式如下:
从文件头开始第1个字节为码表长度,即压缩文件时ASCII码与Huffman码对照表中实际字符个数(即字符频率为非0的字符个数,包括文件结束符)。
从文件头开始第2个字节为相应码表内容,每个码表项依次为每个字符的ASCII码与相应的Huffman码信息,码表项按ASCII码由小至大排列。每个码表项由3部分组成,第1部分为字符ASCII码(占一个字节)、第2部分为其对应Huffman码(二进制)长度(占一个字节)、第3部分为其对应Huffman码(占不定长位),每个码表项要占完整字节(为3个以上字节),多余位要补0。
假设,字符e的Huffman编码为110,由于e的ASCII码为十进制101(二进制01100101)、Huffman编码长度为3位(对应一字节8位二进制值为00000011),则其码表项占3个字节(多余5位补0),内容为:
若字符x的Huffman编码为10011110,由于x的ASCII码为十进制120(二进制01111000)、Huffman编码长度为8位(对应一字节8位二进制值为00001000),则其相应码表项正好占3个字节,内容为:
对hzip.exe工具压缩后的.hzip文件执行相应的解压命令将得到压缩前文件。
若命令行参数错误,则错误提示信息显示在屏幕上。
样例
若当前目录下myfile.txt中内容如下:
I will give you some advice about life. Eat more roughage; Do more than others expect you to do and do it pains; Remember what life tells you; Do not take to heart every thing you hear. Do not spend all that you have. Do not sleep as long as you want.
在命令行执行如下命令:
>hzip myfile.txt
将会在当前目录下生成一个myfile.hzip压缩文件,该文件若用二进制文件查看器查看,其内容如下:
在命令行对所生成的压缩文件myfile.hzip执行如下命令:
>hzip -u myfile.hzip
将在当前目录下得到压缩前文件myfile.txt。
问题分析
解压的思路与压缩恰恰相反。主题思路如下:
- 读取文件中出现的字符、Huffman码。按照格式,第一个字符ASCII码是出现字符个数n。接下来继续读取3n个字符,分为n组,每一组中,第一个字符为出现的字符,第二个字符ASCII值为Huffman码长度len,第三个字符化为二进制后的前len位为该字符对应的Huffman码;
注意:读取时循环条件要设置为while(!feof(fp)),而不能是while((c=fgetc(fp))!=EOF)。因为你无法确定Huffman码中能否拼凑出一个11111111出来,它的十进制为-1,与EOF同值,极有可能会导致读取文件提前结束! - 生成Huffman树。根据每个字符的Huffman码,路径向左为0,向右为1。很容易建造出Huffman树,并且在树的叶结点处记录对应的字符;
- 生成文章。创建空字符串s,继续读取文章中的字符,并将其ASCII码转化为二进制形式,存入s中。存储完毕之后,通过递归的方式可以将s中的Huffman码对应为具体的路径,走到叶结点时向文件中输出对应的字符,从而实现解压缩功能!
同样地,对于篇幅特别特别长的文章,s的体量一如既往得大,拖着上千万的数组运算属实时间过长。那么借鉴一下压缩思路,我们就会再次想到一个很妙的方法:
对于存储数组s,在累积Huffman码的时候,对长度加以限制。当长度超过MAXSIZE时,全部转化为十进制字符进行输出,使用过的部分删除,只留下剩余部分。此举成功地将最影响程序运行时间的数组长度过长问题解决了,AC唾手可得~
需要注意的是,MAXSIZE需要在一个合适的范围内。因为采取递归输出的形式,MAXSIZE过大循环里面加上递归仍然时间很大,而过小则会导致输出的那个循环运行次数过多。作者推荐MAXSIZE定为1000。
代码实现
#include <stdio.h>//本程序仅实现解压缩功能
#include <string.h>
#include <stdlib.h>
#include <math.h>
#define MAXSIZE 10105
typedef struct node
{
char c;
int flag;
struct node* left,* right;
}tnode,*ttree;
ttree Root=NULL;
char s[10005],HCode[128][50],r[MAXSIZE],code[MAXSIZE];
int sum,maxlen=0,minlen,k=0,l=0,m=0;
FILE * Src,* Obj;
void Read();
void Hcode(int n);
void print1();
void unhzip();
void build();
void visit(ttree p,int i);
int main(int argc,char ** argv)
{
char txt[MAXSIZE]={},hzip[MAXSIZE]={};
if (argc==1||argc==3&&argv[1][strlen(argv[1])-1]!='u')
{
printf("Usage: hzip.exe [-u] <filename>");
return 0;
}
if (argc==2&&argv[1][strlen(argv[1])-1]!='t'||argc==3&&argv[2][strlen(argv[2])-1]!='p')
{
printf("File extension error!");
return 0;
}
strcpy(txt,argv[2]);
strcpy(hzip,argv[2]);
hzip[strlen(argv[2])-4]='t';
hzip[strlen(argv[2])-3]='x';
hzip[strlen(argv[2])-2]='t';
hzip[strlen(argv[2])-1]='\0';
if((Src=fopen(txt,"r"))==NULL) {
fprintf(stderr, "%s open failed!\n", "input.txt");
return 1;
}
if((Obj=fopen(hzip,"w"))==NULL) {
fprintf(stderr, "%s open failed!\n", "output.txt");
return 1;
}
puts(txt);
puts(hzip);
Read();//读取文件中字符
build();//建立Huffman树
unhzip();//生成解压文件
fclose(Src);
fclose(Obj);
return 0;
}
void Read()
{
int i=0,j,times,u,v;
unsigned char d,hc;
d=fgetc(Src);
sum=d;
for (i=0;i<sum;i++)
{
d=fgetc(Src);
hc=d;
d=fgetc(Src);
times=d;
u=times%8;
v=times/8;
if (u>0) v++;
for (j=0;j<v;j++)
{
d=fgetc(Src);
Hcode(d);
}
code[times]='\0';
strcpy(HCode[hc],code);
memset(code,0,sizeof(char));
}
}
void build()
{
int i,j;
ttree p,q;
Root=(ttree)malloc(sizeof(tnode));
Root->left=NULL;
Root->right=NULL;
for (i=0;i<128;i++)
{
p=Root;
if (HCode[i][0]!='\0')
{
for (j=0;j<strlen(HCode[i]);j++)
{
if (HCode[i][j]=='0'&&p->left==NULL)
{
q=(ttree)malloc(sizeof(tnode));
q->left=NULL;
q->right=NULL;
p->left=q;
p=p->left;
}
else if (HCode[i][j]=='0'&&p->left!=NULL)
{
p=p->left;
}
else if (HCode[i][j]=='1'&&p->right==NULL)
{
q=(ttree)malloc(sizeof(tnode));
q->left=NULL;
q->right=NULL;
p->right=q;
p=p->right;
}
else
{
p=p->right;
}
}
p->c=i;
}
}
}
void unhzip()
{
unsigned char d;
char e;
int i,len;
while (!feof(Src))
{
//putchar(d);
e=fgetc(Src);
d=e;
Hcode(d);
if (strlen(code)>=1000)
{
while(l!=-1)
{
m=k;
visit(Root,k);
}
if (k>=strlen(code)) memset(code,0,sizeof(char));
else
{
len=strlen(code);
for (i=k;i<len;i++)
{
code[i-k]=code[i];
}
code[len-k]='\0';
}
k=l=m=0;
}
}
while(l!=-1)
{
m=k;
visit(Root,k);
}
k=l=m=0;
printf("%s",code);
}
void visit(ttree p,int i)
{
if (i>=strlen(code))
{
l=-1;
return;
}
if (p->left==NULL&&p->right==NULL)
{
fputc(p->c,Obj);
putchar(p->c);
k=i;
return;
}
if (code[i]=='0') visit(p->left,i+1);
else visit(p->right,i+1);
return;
}
void Hcode(int n)
{
char ss[MAXSIZE]={},t;
int i=0,j;
while (i<8)
{
ss[i++]=n%2+'0';
n/=2;
}
for (i=0,j=strlen(ss)-1;i<=j;i++,j--)
{
t=ss[i];
ss[i]=ss[j];
ss[j]=t;
}
strcat(code,ss);
}
void print1()
{
int i;
for (i=0;i<128;i++)
{
if (HCode[i][0]!='\0')
{
printf("%c:%s\n",i,HCode[i]);
}
}
}
感谢阅读~