哈夫曼是一种常用的压缩方法。是1952年为文本文件建立的,其基本原理是频繁使用的数据用较短的代码代替,很少使用的数据用较长的代码代替,每个数据的代码各不相同。这些代码都是二进制码,且码的长度是可变的。如: 有一个原始数据序列,ABACCDAA则编码为A(0),B(10),C(110),(D111),压缩后为010011011011100。
产生霍夫曼编码需要对原始数据扫描两遍,第一遍扫描要精确地统计出原始数据中的每个值出现的频率,第二遍是建立霍夫曼树并进行编码,由于需要建立二叉树并遍历二叉树生成编码,因此数据压缩和还原速度都较慢,但简单有效,因而得到广泛的应用。 哈夫曼算法在改变任何符号二进制编码引起少量密集表现方面是最佳的。然而,它并不处理符号的顺序和重复或序号的序列。
哈夫曼压缩,首先用ASCII值初始化511个哈夫曼节点,然后,计算在输入缓冲区数据中,每个ASCII码出现的频率。然后,根据频率进行排序,现在,构造哈夫曼树,获取每个ASCII码对应的位序列,构造哈夫曼树,将所有的节点放到一个队列中,用一个节点替换两个频率最低的节点,新节点的频率就是这两个节点的频率之和。这样,新节点就是两个被替换节点的父节点了。如此循环,直到队列中只剩一个节点(树根)。压缩的最后一步是将每个ASCII编码写入输出缓冲区中 哈夫曼解压缩,将输入缓冲区中的每个编码用对应的ASCII码逐个替换就可以了。只要记住,这里的输入缓冲区是一个包含每个ASCII值的编码的位流。因此,为了用ASCII值替换编码,我们必须用位流搜索哈夫曼树,直到发现一个叶节点,然后将它的ASCII值添加到输出缓冲区中。
/*****************************************************************/
/*****************************************************************/
/**程序: compress.c **/
/**功能: 压缩与解压缩单个文件 **/
/**详情: 利用霍夫曼算法生成输入文件的霍夫曼编码,并转换成二进制**/
/** 字节流输出从而达到压缩的效果;解压缩则是从压缩文件中读**/
/** 入霍夫曼树信息,从而还原为原编码,达到解压缩的目的。 **/
/** 压缩文件结构 **/
/** 偏移量 存储信息类型 **/
/** 0~3 文件头标志 **/
/** 4 源文件名长度:n_s **/
/** 5~4+n_s 文件名 **/
/** 5+n_s~8+n_s 源文件长度 **/
/** 9+n_s~1028+n_s huffman树非叶子节点孩子信息 **/
/** 1029+n_s~FILE_END 源文件的huffman二级制编码信息 **/
/**环境: AM2_4000+ & Windows_Server_2008 & VC++_2008 **/
/**其他: davelv @ 2008-12-18 **/
/*****************************************************************/
/*****************************************************************/
////////////////////////////////////////////////////////////////////
//// 头文件、自定义类型、预定义宏常量 ////
////////////////////////////////////////////////////////////////////
#define _CRT_SECURE_NO_DEPRECATE//VC2005或更新的编译器不显示I/O安全警告
#include
#include
#include
typedef unsigned int UINT;
typedef unsigned char UCHAR;
typedef unsigned short USHORT;
typedef struct node
{
long w;//权
short p,l,r;//父亲,左孩子,右孩子
}htnode,*htnp;//霍夫曼树的结点
typedef struct huffman_code
{
UCHAR len;//长度
UCHAR *codestr;//字符串
}hufcode;//字符版本的霍夫曼编码
#define OK 1
#define ERROR -1
#define UNUSE -1
#define ARGS_ERR -2//参数错误
#define FILE_ERR -3//文件错误
#define HEAD_ERR -4//头标错误
#define MALLOC_ERR -5//内存分配错误
#define HUFTREE_ERR -6//霍夫曼树错误
#define HUFCODE_ERR -7//霍夫曼编码错误
#define CHAR_BITS 8//一个字符中的位数
#define INT_BITS 32//一个整型中的位数
#define CODE_SIZE 256//霍夫曼编码个数
#define CACHE_SIZE 256//I/O缓存大小
#define UINT_SIZE sizeof(UINT)
#define UCHAR_SIZE sizeof(UCHAR)
#define USHORT_SIZE sizeof(USHORT)
#define ZIP_HEAD 0xFFFFFFFF//压缩文件头标
#define MAX_NAME_LEN 512
////////////////////////////////////////////////////////////////////
//// 函数声明 ////
////////////////////////////////////////////////////////////////////
//压缩相关函数
UCHAR chars_to_bits(const UCHAR chars[CHAR_BITS]);//将一个字符数组转换成二进制数字@ write_compress_file()
int search_set(htnp ht,int n);//查找当前最小权值的霍夫曼树节点并置为占用@create_huffmantree()
int create_huffmantree(long w[],int n,htnode ht[]);//生成霍夫曼树@compress()
int encode_huffmantree(htnp htp,int n,hufcode hc[]);//生成霍夫曼编码@compress()
long calc_data_frequency(FILE *in,long frequency[]);//计算每个不同字节的频率以及所有的字节数@compress()
int compress(char *source_filename,char *obj_filename);//处理压缩文件的流程@process_args()
int c_initial_files(char *source_filename,FILE **inp,char *obj_filename,FILE **outp);//为处理压缩流程初始化文件@compress()
int write_compress_file(FILE *in,FILE *out,htnp ht,hufcode hc[],char* source_filename,long source_filesize);//写压缩文件@compress()
//解压缩相关函数
void get_mini_huffmantree(FILE* in,short mini_ht[][2]);//从待解压缩文件中得到一个小型的huffman树@decompress()
int decompress(char *source_filename,char *obj_filename);//处理解压缩文件的流程@process_args()
int d_initial_files(char *source_filename,FILE **inp,char *obj_filename,FILE **outp);//为处理解压缩流程初始化文件@decompress()
int write_decompress_file(FILE *in,FILE* out,short mini_ht[][2],long bits_pos,long obj_filesize);//写解压缩文件@decompress()
//辅助函数
void print_help();//在屏幕上显示帮助@process_args(),main()
void process_error(int error_code);//处理函数中返回的错误代码@process_args(),main()
void process_args(char *first,char*second,char*third);//处理命令行参数@main()
char *create_default_obj_filename(char *source_filename,char* obj_filename);//生成一个默认的文件名@c_initial_files()
////////////////////////////////////////////////////////////////////
//// 压缩相关函数 ////
////////////////////////////////////////////////////////////////////
/****************************************************************/
/*函数: c_initial_files() */
/*功能: 初始化并打开压缩功能所需所有文件 */
/*参数: char *source_filename 源文件名字符串 */
/* FILE **inp 指向输入文件指针的指针 */
/* char *obj_filename 目标文件名字字符串 */
/* FILE **outp 指向输出文件指针的指针 */
/*返回: int OK 函数成功运行 */
/* 其他 出错 */
/****************************************************************/
int c_initial_files(char *source_filename,FILE **inp,char *obj_filename,FILE **outp)
{
char temp_filename[MAX_NAME_LEN];
if(source_filename==NULL)
{
return FILE_ERR;//空名字返回错误
}
//当目标文件名没分配时
if(obj_filename==NULL)
{
obj_filename = temp_filename;
create_default_obj_filename(source_filename,obj_filename);//生成默认名字
}
if(strcmp(source_filename,obj_filename)==0)
{
return FILE_ERR;//重名返回错误
}
printf("待压缩文件:%s,压缩文件:%s\n",source_filename,obj_filename);
if((*outp=fopen(obj_filename,"wb"))==NULL)
{
return FILE_ERR;//不能读,返回错误
}
if((*inp=fopen(source_filename,"rb"))==NULL)
{
return FILE_ERR;//不能写,返回错误
}
return OK;
}
/****************************************************************/
/*函数: calc_data_frequency() */
/*功能: 计算输入文件中不同字节的出现的次数以及所有的字节数 */
/*参数: FILE *in 输入文件指针 */
/* long frequency[] 保存不同字节出现次数的数组 */
/*返回: long filesize 输入文件总长度 */
/****************************************************************/
long calc_data_frequency(FILE *in,long frequency[])
{
int i,read_len;
UCHAR buf[CACHE_SIZE];//I/O缓存数组
long filesize;//总长
for(i=0;ilen)
{ //获取本次复制字符的长度为 可用写字符长度与可用huffman编码长度中的较小者
copy_char_counter= (CHAR_BITS-write_char_counter > cur_hufcode->len-code_char_counter ?
cur_hufcode->len-code_char_counter : CHAR_BITS-write_char_counter);
//复制一段字符
memcpy(write_chars+write_char_counter,cur_hufcode->codestr+code_char_counter,copy_char_counter);
write_char_counter+=copy_char_counter;//写字符计数器增加
code_char_counter+=copy_char_counter;//编码字符计数器增加
//当写字符计算器满=8时
if(write_char_counter==CHAR_BITS)
{
write_char_counter=0;//写字符计数器清0
//当写缓存满时
write_buf[write_counter++]=chars_to_bits(write_chars);//转化写字符为二进制数并存入写缓存
if(write_counter==CACHE_SIZE)
{
fwrite(write_buf,1,CACHE_SIZE,out);//输出到文件
write_counter=0;//写缓存清0
}
}
}
}
}
fwrite(write_buf,1,write_counter,out);//写缓存中剩余数据输出到文件
//当写字符数组中还有字符未转换
if(write_char_counter!=0)
{
write_char_counter=chars_to_bits(write_chars);//转化为二级制数据
fwrite(&write_char_counter,1,1,out);//输出到文件
}
return OK;
}
/****************************************************************/
/*函数: compress() */
/*功能: 处理压缩文件的流程 */
/*参数: char *source_filename 源文件名字符串 */
/* char *obj_filename 目标文件名字字符串 */
/*返回: int OK 函数成功运行 */
/* 其他 出错 */
/****************************************************************/
int compress(char *source_filename,char *obj_filename)
{
FILE *in,*out;
int error_code,i;
float compress_rate;
hufcode hc[CODE_SIZE];
htnode ht[CODE_SIZE*2-1];
long frequency[CODE_SIZE ],source_filesize,obj_filesize=0;
//处理待压缩与压缩文件文件
error_code=c_initial_files(source_filename,&in,obj_filename,&out);
if(error_code!=OK)
{
return error_code;
}
puts("文件打开成功,开始读取文件信息...");
//处理各个字符的频率
source_filesize=calc_data_frequency(in,frequency);
puts("文件读入完成,开始分析文件信息...");
printf("文件大小 %ld 字节\n",source_filesize);
//生成huffmantree
error_code=create_huffmantree(frequency,CODE_SIZE,ht);
if(error_code!=OK)
{
return error_code;
}
puts("霍夫曼树建立成功,开始霍夫曼编码...");
//生成huffmancode
error_code=encode_huffmantree(ht,CODE_SIZE,hc);
if(error_code!=OK)
{
return error_code;
}
//处理压缩文件长度
for(i=0;i>=1)
{
cur_pos=((read_buf[read_counter]&convert_bit)==0?mini_ht[cur_pos][0]:mini_ht[cur_pos][1]);//按位查找huffmantree节点,0左,1右
if(cur_pos