BUAA数据结构作业——基于Huffman码的文件解压工具

目录

前言

题目

问题描述

输入形式

输出形式

样例

问题分析

代码实现


前言

上一篇文章我们进行文章的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。

问题分析

解压的思路与压缩恰恰相反。主题思路如下:

  1. 读取文件中出现的字符、Huffman码。按照格式,第一个字符ASCII码是出现字符个数n。接下来继续读取3n个字符,分为n组,每一组中,第一个字符为出现的字符,第二个字符ASCII值为Huffman码长度len,第三个字符化为二进制后的前len位为该字符对应的Huffman码;
    注意:读取时循环条件要设置为while(!feof(fp)),而不能是while((c=fgetc(fp))!=EOF)。因为你无法确定Huffman码中能否拼凑出一个11111111出来,它的十进制为-1,与EOF同值,极有可能会导致读取文件提前结束!
  2. 生成Huffman树。根据每个字符的Huffman码,路径向左为0,向右为1。很容易建造出Huffman树,并且在树的叶结点处记录对应的字符;
  3. 生成文章。创建空字符串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]);
		}
	}
}

感谢阅读~

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值