Huffman压缩和解压
一、 需求分析
1. 本演示程序中,模拟的是小写26个英文字母的Huffman压缩,字母小随机函数随机产生,后统计字母个数建立Huffman树,用建立Huffman树将字母转为二进制流,再将二进制流每次分8个转为一个Unsigned Char写入物理内存!
2. 解压时建立同样的Huffman树或读取已保存的Huffman树,通过将读取的Unsigned Char转为比特流,再由比特流得到原被压缩的字母
3. 程序执行的命令有:随机文件的产生,文件统计,Huffman树的建立,压缩,解压,程序的运行过程和其它信息的文件存储!
4. 程序测试:运行程序,产生随机文件input.txt,压缩时得到的比特流bytes.txt,压缩文件compress.txt,解压时到得的比特流uncompress.txt,解压后文件output.txt和程序的运行过程文件Run.txt,其中红色文件为可无文件
二、 概要设计
1. 为达到压缩效果,采用Huffman树来进行编码压缩
2. Huffman编码后若直接保存比特流至txt将达不到压缩的效果,因此建议将比特流转为Unsigned char 来间接存储以达到明显的压缩效果
3. 解压时因用到之前的Huffman树,因此在压缩时有必要保存树的状态
4. Huffman树在压缩时直接使用对应编码即可,在解压时通过遍历树来得到被编码的字母
三、 详细设计
1. 树结构体的定义和全局变量的定义:
typedef struct HTNode{/*Huffman Tree 的结构定义*/
unsigned int weight;
unsigned int parent, lchild, rchild;
}HTNode,* HuffmanTree;
typedef char** HuffmanCode;/*指向字符指针的指针的数据的定义*/
int stat[27];/*存储总数,存储26个字母出现的次数*/
HuffmanTree HT;
HuffmanCode HC;
2. 随机文件的产生函数:
void CreatFile(){/*自动产生文件*/
FILE *Fopen,*Fout;
int i;
srand( (unsigned)time(NULL));
Fout=fopen("./Run.txt","w");
fprintf(Fout,"NO.1: Creating a file by rand()"n");
Fopen = fopen("./input.txt", "w");
for(i=0; i<10000; i++){
char ch=(char)(97+rand()%26);
fputc(ch, Fopen);
}
fclose(Fopen);
fclose(Fout);
}
3. 字母个数的统计:
void ReadStat(char *Filename){/*每个字符出现的次数统计*/
int c;
FILE *Fopen=fopen(Filename, "r");
while((c=fgetc(Fopen))!=EOF){
stat[c%96]++;
stat[0]++;
}
}
4. 选择HT[1…i-1]中Parent为0且Weight最小的一个点的函数:
void Select(HuffmanTree HT, int d, int *s){
int temp=0,i;
for(i=1; i<=d; i++){
if(HT[i].parent!=0)
continue;
if(temp==0)
temp=i;
else if(HT[temp].weight>HT[i].weight)
temp=i;
}
*s=temp;
}
5. Huffman树的建立函数:
void HuffmanCoding(int* w, int n){
int m= 2*n-1;
int i, s1, s2, start;
unsigned int c, f;
char *cd;
if(n<=1)
return;
HT=(HTNode*)malloc((m+1)*sizeof(HTNode));/*0号单元未用*/
for(i=1; i<=n; i++, w++){
/***p={*w,0,0,0}; 初始化*/
HT[i].weight=*w;
HT[i].parent=0;
HT[i].lchild=0;
HT[i].rchild=0;
}
for(; i<=m; i++){
/**p={0,0,0,0};*/
HT[i].weight=*w;
HT[i].parent=0;
HT[i].lchild=0;
HT[i].rchild=0;
}
for(i=n+1; i<=m; i++){
Select(HT, i-1, &s1);/*选择HT[1…i-1]中Parent为0且Weight最小的一个点*/
HT[s1].parent=i;
Select(HT, i-1, &s2);
HT[s2].parent=i;
HT[i].lchild=s1;/*左右孩子对编码没有影响,也说明的Huffman树的不唯一性*/
HT[i].rchild=s2;
HT[i].weight= HT[s1].weight + HT[s2].weight;
}
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;
for(c=i, f=HT[i].parent; f!=0; c=f, f=HT[f].parent)
if(HT[f].lchild==c)
cd[--start]='0';
else
cd[--start]='1';
HC[i]=(char*)malloc((n-start)*sizeof(char));
strcpy(HC[i], &cd[start]);
}
free(cd);
}
6. Huffman树的存储:
void WriteCode(){
FILE *Fopen;
int i;
Fopen=fopen("./Run.txt","a+");
fprintf(Fopen,"NO.2: The array of the structure of HuffmanTree:"n");
for(i=1;i<52;i++)
fprintf(Fopen,"%-2d: p:%-3d w:%-5d l:%-3d r:%-3d"n",i, HT[i].parent,HT[i].weight,HT[i].lchild,HT[i].rchild);
fprintf(Fopen,"NO.3: The Huffman codes:"n");
for(i=1;i<27;i++)
fprintf(Fopen,"%c: %3d %s"n",'a'+i-1,stat[i],HC[i]);
}
7. 输出压缩前应该得到的比特流:
void OutCompress(){
FILE *Fin,*Fout;
int c;
Fin=fopen("./input.txt","r");
Fout=fopen("./bytes.txt","w");
if(Fin==NULL)
printf("Can't find the file of 'input'!"n");
while((c=fgetc(Fin))!=EOF){/*output the bit stream*/
fprintf(Fout,"%s",HC[c-96]);
}
fclose(Fin);
fclose(Fout);
}
8. 压缩函数:
void Compress(){
FILE *Fin,*Fout;
unsigned char out=0;
char c,*buf;
int i,count=0;
buf=(char *)malloc(sizeof(char)*20);
Fin=fopen("./input.txt","r");
Fout=fopen("./compress.txt","wb");
if(Fin==NULL)
printf("Can't find the file of 'input'!"n");
while((c=fgetc(Fin))!=EOF){/*output the bit stream*/
buf=HC[c-96];
for(i=0;buf[i]!='"0';i++){
if(count==8){
fprintf(Fout,"%c",out);
count=0;
out=0;
}
out<<=1;
count++;
if(buf[i]=='1')
out=out|1;
}
}
if(count!=8){
out<<=(8-count);
fprintf(Fout,"%c",out);
}
fclose(Fin);
fclose(Fout);
}
9. 解压函数:
void UnCompress(){
FILE *Fin,*Fout,*Fout1;
unsigned char c=0,*buf;
int i,k,t=51;
buf=(char *)malloc(sizeof(char)*10);
Fin=fopen("./compress.txt","rb");
Fout=fopen("./uncompress.txt","wb");
Fout1=fopen("./output.txt","wb");
if(Fin==NULL)
printf("Can't find the file of 'input'!"n");
while(!feof(Fin)){/*output the bit stream*/
c=fgetc(Fin);
for(i=7;i>=0;i--){
k=(c>>i)&1;
fprintf(Fout,"%d",k);
if(k==0)
t=HT[t].lchild;
else
t=HT[t].rchild;
if(HT[t].lchild==0&&HT[t].rchild==0){
fprintf(Fout1,"%c",96+t);
t=51;
}
}
}
fclose(Fin);
fclose(Fout);
}
10. 主函数入口
int main(int arg, char *argv[]){/*存放Huffman编码*/
CreatFile();
ReadStat("./input.txt");
HuffmanCoding(&stat[1], 26);
WriteCode();
OutCompress();
Compress();
UnCompress();
system("Echo Pree any key to kill the program");
system("PAUSE>NUL 2>NUL");
}
四、 调试分析
1. 二进制流与unsigned char之间的转换,采用的是位操作,而不是进制的转换,导致失败
2. 位操作的使用出错,将c<<1错误理解为对C的左移,导致无法成功压缩,后改为C=<<1即可
3. 文件的读入方式有二进制读入和文本读入,当压缩文件中写入^Z(文本文件结束标志)后,文本方式读取压缩文件失败,最后使用二进制读取成功
4. 文件的压缩效率问题上我考虑过用unsigned int来替换unsigned char,但结果是失败的,压缩率反而降低
5. 由于比特流中二进制数%sizeof(unsigned char)不一定为零,此异常(解压文件结尾处会多出2个字符)处理仍没有得到有效解决!
6. 因为输入文件是随机为生,每个字母出现的频率相近,因此得到的Huffman编码长度也基本相同,在这种情况也就是最差的情况下,最后压缩率达到60%,让人振奋!
五、 测试结果:
经多次按要求合法测试,测试程序执行的无误,压缩率也在60%以下,下面是测试结果的一个范例:
其中输入文件大小为10,000个随机字母,大小为:10,000 字节
产生的压缩文件大小为:5,939 字节
解压后文件大小仍为:10,000 字节
压缩率接近:59.4%
六、 附录(源程序清单)
源代码:Huffman.C 产生的文件有:
input.txt 被压缩文件
output.txt 解压后文件
compress.txt 压缩后文件
bytes.txt 压缩时的比特流
uncompress.txt 解压时得到的比特流
Run.txt 程序运行过程记录
代码如下: