哈夫曼编码及文本文件的压缩解压(c++SourceCode)

 

	哈夫曼编码是一种编码方式,是可变字长编码(VLC)的一种。以哈夫曼树—即最优二叉树,带权路径长度最小的二叉树,经常应用于数据
压缩。 在计算机信息处理中,“哈夫曼编码”是一种一致性编码法(又称"熵编码法"),用于数据的无损耗压缩。这一术语是指使用一张特殊的
编码表将源字符(例如某文件中的一个符号)进行编码。这张编码表的特殊之处在于,它是根据每一个源字符出现的估算概率而建立起来的(出
现概率高的字符使用较短的编码,反之出现概率低的则使用较长的编码,这便使编码之后的字符串的平均期望长度降低,从而达到无损压缩数
据的目的)。这种方法是由David.A.Huffman发展起来的。 例如,在英文中,e的出现概率很高,而z的出现概率则最低。当利用哈夫曼编码
对一篇英文进行压缩时,e极有可能用一个位(bit)来表示,而z则可能花去25个位(不是26)。用普通的表示方法时,每个英文字母均占用一
个字节(byte),即8个位。二者相比,e使用了一般编码的1/8的长度,z则使用了3倍多。倘若我们能实现对于英文中各个字母出现概率的较
准确的估算,就可以大幅度提高无损压缩的比例。
	其中哈夫曼编码的构造过程为:

	(1)初始化,根据符号概率的大小按由大到小顺序对符号进行排序。
	(2)把概率最小的两个符号组成一个新符号(节点),即新符号的概率等于这两个符号概率之和。
	(3)重复第2步,直到形成一个符号为止(树),其概率最后等于1。
	(4)从编码树的根开始回溯到原始的符号,并将每一左分枝赋值为0,右分枝赋值为0。
	前三步为构造哈夫曼树过程,既而在第四步遍历构造出的哈夫曼树,得到各字符的最优前缀码.	
	为了快速实现取集合中的最小值及向集合中插入新的元素,可以使用二叉堆来实现,二叉堆的定义及实现请另行参考.(注:由于ASCII码及
char型数据范围的限制,编码过程中生成的哈夫曼树不会非常大,所以生成哈夫曼树所需要的时间在整个压缩过程中显得微不足道,可用其它方
式来生成哈夫曼树.)
以一棵生成的哈夫曼树为例(图片来自:http://blog.163.com/yuang_yu_ping/blog/static/4693287620098172049346/):

图片中的哈夫曼树对应的绿色即叶结点为待编码字符,对应的前缀码分别为:
A : 10
C : 01
D : 11
E : 000
M : 001
	在文本文件的压缩过程中,首先取ASCII字符对应的出现次数,然后对 0x00~0xff 这些字符根据出现频率生成对应的前缀码,然后读取
文件 ,为每一个字符进行编码.
	编码问题已经解决了,可是如果要实现文件压缩,压缩文件的编码如何进行存储呢? 首先明确一点,源文件压缩之后的编码对应的是二进制的
位段,而C语言中每个char 类型占用八位的空间,我们可以把编码后的二进制位以八位为一段,生成对应的字符来存储.对最后不足八位的二进制段,
补足八位,用一个字符表示,依次存储这些转化来的字符,即可生成压缩文件.
	另,为了解压的需要,我们要存储编码的信息至压缩文件中,可以采用源文件中各字符出现的频率来记录,解码时读取这些信息,再利用这些信息
重新生成一棵同样的哈夫曼树,另外压缩文件头中存储有最后一个字符实际占用的位数,用以在解压时对最后一个字符正确处理.
	至此,用哈夫曼编码来实现文本文件压缩的大致过程也算明白了,下面来谈一谈具体实现:
	首先是哈夫曼树的结点,
//哈夫曼树结点结构体实现
typedef struct talNode{
	
	unsigned char c;	//叶结点时对应ASCII值
	int weight;			//该结点权值
	int lt,rt;			//左、右结点下标
	
	talNode(){}
	
	talNode(unsigned char _c,int _p):
	c(_c),weight(_p),lt(-1),rt(-1)
	{}
	
	talNode(unsigned char _c,int _p,int l,int r)
		:c(_c),weight(_p),lt(l),rt(r)
	{}
	
	bool operator < (talNode a)
	{//重载运算符“<”用于二叉堆内的比较
		return weight 
  
	生成哈夫曼树时要用到的堆
//实现二叉堆模板类,小顶堆
template 
class CHeap
{
	HeapType *data,tmp;
	int size;
	
	void HeapUp(int ix)
	{//自底向顶维护堆
		int f;
		for(f=F(ix);ix&&data[ix]=size) return ;
		l=L(ix),r=R(ix);
		
		min=data[ix],t=ix;
		
		if(l 
  
	进行压缩工作时的变量:
	HuffNode arr[512];		//哈夫曼树结点数组
	int size;				//哈夫曼树结点个数
	bool code[256][64];		//ASCII对应编码方案
	int lenth[256];			//ASCII对应编码长度

	//lastcodelenth,ps[256],用于存储压缩文件中作为文件头
	int lastcodelenth;		//文件最后一个字符实用几位
	int ps[256];			//ASCII对应出现频率
	int soucelen,targetlen; //源及目标文件长度
	CHuffMan类的声明:
class CHuffMan{
	HuffNode arr[512];		//哈夫曼树结点数组
	int size;				//哈夫曼树结点个数
	bool code[256][64];		//ASCII对应编码方案
	int lenth[256];			//ASCII对应编码长度

	//lastcodelenth,ps[256],用于存储压缩文件中作为文件头
	int lastcodelenth;		//文件最后一个字符实用几位
	int ps[256];			//ASCII对应出现频率
	int soucelen,targetlen; //源及目标文件长度


	//私有成员函数,用于实现内部功能
	void SetHuffTree(int []);			//根据字符频率生成哈夫曼树
	void SetHuffCode(int ,bool [],int );//根据哈夫曼树生成编码方案
	void CreateHuff(int []);			//创建哈夫曼树及编码方案
	void EnCodePre(char []);			//压缩前预处理
	void DeCodePre(FILE *);				//解压前预处理
	void SaveHuffHead(FILE *);			//保存压缩文件头
	void ReadHuffHead(FILE *);			//读取压缩文件头

public:
	CHuffMan(){Clear();}				//构造函数
	~CHuffMan(){}						//析构函数
	
	//公有成员函数,用于提供使用接口
	void Clear();						//清空当前对象内容
	void EnCodeFile(char [],char []);	//编码,用于压缩文件,第一个参数为
										//源文件名,第二个参数为目标文件名
	void DeCodeFile(char [],char []);	//解码,用于解压文件,第一个参数为
										//源文件名,第二个参数为目标文件名
	void GetHuffCode();					//输出ASCII对应编码方案
	int GetSouceLen();					//输出当前工作项的源文件长度
	int GetTargetLen();					//输出当前工作项的目标文件长度
	
};
	思考:从运行效果上来看,压缩的比例不是非常满意,压缩率达不到25%,应该可以采用动态哈夫曼树的编码来对大文件的压缩进行优化,
从而达到更优的压缩率。确定一个初始哈夫曼树,然后根据当前处理过的字符动态的调整哈夫曼树的结构,解压过程同样根据解压过的字符调整哈夫曼树
结构。   压缩与解压的时间效率有待提高,本程序在压缩及解压过程中对硬盘进行了大量的读写,使速度受到影响,可以通过缓冲带还实现提速,即构
造内存缓冲区,每次读写多个字符,既而进行处理。
	
	(注:根据哈夫曼树的构成原理,可以预见一种特殊的情况:哈夫曼树的结构为每一层只有一个叶结点,这种情况下的每个字符对应的前缀码
长度最高会达到256位,在此程序中未进行特殊考虑。)
	运行效果:
 
  
 
  
 
  
 
  
 
  
	
	源代码:

 /***************************************************************** Title: 哈夫曼编码及文件压缩 Author: Dooler IDE: VC++6.0 Date: 2011-5-31 INFO: 实现了两个类, CHeap(二叉堆) CHuffMan(文件压缩) 可对文本文件进行压缩 QQ: 441809866 ******************************************************************/ #include #define ASCIIL 256 #define F(x) ((x-1)>>1) #define L(x) ((x<<1)+1) #define R(x) ((x<<1)+2) #define SWAP(a,b,tmp) {tmp=a;a=b;b=tmp;} using namespace std; //实现二叉堆模板类,小顶堆 template class CHeap { HeapType *data,tmp; int size; void HeapUp(int ix) {//自底向顶维护堆 int f; for(f=F(ix);ix&&data[ix]=size) return ; l=L(ix),r=R(ix); min=data[ix],t=ix; if(l hp; //二叉堆对象 for(int i=0;i1){ arr[size++]=hp.top(); //取出权值最小的两个结点 arr[size++]=hp.top(); hp.insert(HuffNode(0, arr[size-1].weight+arr[size-2].weight, size-1,size-2)); //合并结点,并插入堆 } arr[size++]=hp.top(); //arr[size-1]为哈夫曼树根 } void CHuffMan::SetHuffCode(int ix,bool stk[],int top) { //递归深搜哈夫曼树,生成所有存在的ASCII //的前缀码 if(arr[ix].c){ //如果 if(top){ //此判断用于只含有一类字符的文件 memmove(code[arr[ix].c],stk,sizeof(bool)*top); lenth[arr[ix].c]=top; } else lenth[arr[ix].c]=1; return ; } stk[top]=0; //左子树的边设为0 SetHuffCode(arr[ix].lt,stk,top+1); //递归进入左子树 stk[top]=1; //右子树的边设为1 SetHuffCode(arr[ix].rt,stk,top+1); //递归进入右子树 } void CHuffMan::CreateHuff(int ps[]) { //构造哈夫曼树及前缀码 bool stk[64]; SetHuffTree(ps); //根据字符频率构造哈夫曼树 SetHuffCode(size-1,stk,0); //根据哈夫曼树生成前缀码 } void CHuffMan::EnCodePre(char sfilename[]) { //压缩文件预处理,读取字符出现频率 FILE *fp; //及构造哈夫曼树及前缀码 int c; fp=fopen(sfilename,"rb"); if(fp==NULL){ cout<<"读取文件错误"<0){ //如果前缀码不空 printf("%c : ",i); //输出ASCII码 for(int j=0;j=l;i--){ //按位取出前缀码 tmp[i]=c&1;c>>=1; }l+=8; while(l>=32){ //如果当前前缀码段超出一定的长度,则取出前缀码 //进行解码 for(i=0,cur=arr[size-1];!cur.c;i++) cur=tmp[i]?arr[cur.rt]:arr[cur.lt];//找到前缀码段对应第一个字符 fprintf(fpt,"%c",cur.c); //输出至目标文件 l-=i;targetlen++; //前缀码段减去当前字符前缀码长度 memmove(tmp,tmp+i,sizeof(bool)*l); //数组顺移至开头,即从0开始记录当前的 //前缀码段 }c=t; } for(i=l+7;i>=l;i--){ //对最后一个字符做特殊处理 tmp[i]=c&1; //取出每一位 c>>=1; } l+=lastcodelenth; //只利用最后一个字符的前lastcodelenth位 while(l){ //输出剩余的前缀码段对应的字符 for(i=0,cur=arr[size-1];!cur.c;i++) cur=tmp[i]?arr[cur.rt]:arr[cur.lt]; fprintf(fpt,"%c",cur.c);l-=i;targetlen++; memmove(tmp,tmp+i,sizeof(bool)*l); } fclose(fp);fclose(fpt); //关闭文件 } bool Menu(int &op) { system("cls"); printf("|/t哈夫曼编码实现文件压缩/t|/n"); printf("功能:/n"); printf("_________________________________/n"); printf("|/t1、/t压缩文件/t|/n"); printf("|/t2、/t解压文件/t|/n"); printf("|/t3、/t输出编码方案/t|/n"); printf("|/t0、/t退出 /t|/n"); printf("---------------------------------/n"); do{ printf("请选择:"); scanf("%d",&op); }while(op<0||op>3); return op?true:false; } int main() { int op; char file1[32],file2[32]; CHuffMan work; char step[2]; while(Menu(op)){ switch(op){ case 1: printf("请输入待压缩文件名(.txt):"); scanf("%s",file1); printf("请输入压缩文件名(.huf):"); scanf("%s",file2); work.Clear(); work.EnCodeFile(file1,file2); printf("源文件长度:/t%d/n",work.GetSouceLen()); printf("目标文件长度:/t%d/n",work.GetTargetLen()); break; case 2: printf("请输入待压缩文件名(.huf):"); scanf("%s",file1); printf("请输入压缩文件名(.txt):"); scanf("%s",file2); work.Clear(); work.DeCodeFile(file1,file2); printf("源文件长度:/t%d/n",work.GetSouceLen()); printf("目标文件长度:/t%d/n",work.GetTargetLen()); break; case 3: work.GetHuffCode(); break; } puts("按任意键继续..."); gets(step); gets(step); } return 0; }

你可能感兴趣的:(My~Way,程序员)