哈夫曼编码--压缩与解压
来源:互联网 发布:神仙劫进阶数据 编辑:程序博客网 时间:2024/05/30 04:36
- 算法描述
- 哈夫曼编码算法的定义
- 哈夫曼编码编码方式
- 压缩
- 压缩基本方法
- 关于头文件
- 解压缩
- 程序执行基本界面
算法描述
哈夫曼编码算法的定义
哈夫曼编码,又称霍夫曼编码,是一种编码方式,为可变字长编码(VLC)。该方法完全依据字符出现概率来构造异字头的平均长度最短的码字,有时称之为最佳编码。
哈夫曼编码编码方式
该编码方式根据不同字符出现的概率来进行构建最佳二叉树,所有的字符都位于叶子节点,规定从根节点开始,往左走为0,往右走为1,通过这种方式,可以对所有的字符进行重新的编码,从而实现码字的平均长度最短。
具体编码方式如下:
1. 从列表中选出两个具有出现次数最少的符号,以这两个符号作为孩子节点,创建一棵哈夫曼子树,并为这两个及诶单创建一个父亲节点。
2. 以孩子结点的出现次数的总和作为父亲节点出现的次数,将父亲节点插入字符的列表中。
3. 从列表中将孩子结点删除(利用是否有父亲节点区分是否需要考虑)
4. 根据从根到叶子节点的路径为每一叶子节点赋予一个码字。
哈夫曼树创建图示
压缩
压缩基本方法
利用一个例子进行讲述,我们压缩以下一段字符串:
按照图4中得到的编码可以得到以下的一段01字符串:
由于每个ASCII字符都是大小8位的字符,所以按照每8个01字符生成一个ASCII字符,不足8个的在其后用0补充完整,可以得到:
也就是说之前的11个字符就可以用4个字符来代替。从而实现有效的压缩。由于最后进行了0拓展,所以在解压的时候也会将这些编码进行解压,如果不凑巧的话,可能会多处一两个甚至更多的字符。因此我们需要压入记录原文件的大小,进行对比判断是否压缩完成。
关于头文件
定义:头文件是指压缩文件中开始部分,包含原文件信息及压缩文件的字符编码信息编码。头文件不同,所得的文件解压方式不同,大小也会有所不同,甚至会变大。
意义:由于不同的文件压缩所用编码不同,必须将对应的编码写入到压缩文件中,才能在解压时对原文件的还原。如果没有压缩时所使用的编码信息,那么压缩出的文件就是没有用的。
频数的位置对应ASCII码,如第0个频数表示ASCII码为0字符的频数。读取这些频数,可以有效地恢复哈夫曼树,以达到对压缩文件的解压。如果频数用unsigned long格式的数据,就可以记录很大的数据,头文件仅占有2k。而利用该头文件格式,最大可压缩文件大小为:256 * 2^64 B = 2^52GB。
解压缩
解压大体过程:
1. 利用头文件中的字符频数构建原来额字母表。
2. 利用字母表构建哈夫曼树。
3. 以字符的方式读取原文件压缩内容为01串。
4. 利用哈夫曼树进行文件解压。
由于我的笔记本没有安装WinRar,所以无法调用rar指令进行rar压缩,这里使用Zip压缩进行对比。使用的是360压缩。
程序执行基本界面
#include<iostream>#include<cstdio>#include<cstring>#include<string.h>#include<string>#include<conio.h>#include<stdlib.h>#include<io.h>#include <ctime>using namespace std;// 256 个字符,最多有 2 * 256 - 1 = 511个节点,这里用 512 + 5#define MAXLEN 512+5#define ASCLLNUM 256int test = false;//哈夫曼树节点typedef struct huffNode{ int parent,lchild,rchild; //二叉树关系 unsigned long count; //符号个数 unsigned char alpha; //符号 char code[MAXLEN]; //编码}HuffNode;//存放文件中各个字符,及其出现次数typedef struct ascll{ unsigned char alpha; //符号 unsigned long count; //符号个数}Ascll;//这个存放字符及字符对应的编码/*typedef struct huffTable{ unsigned char alpha; //符号 char code[MAXLEN]; //编码}HuffTable;*///展示交互界面void showGUI(){ cout<<" 压缩、解压缩工具 \n\n"; cout<<"功能:"<<endl; cout<<" 1.压缩"<<endl; cout<<" 2.解压缩"<<endl; cout<<" 3.输出编码"<<endl; cout<<" 4.测试ZIP"<<endl; cout<<" 5.退出"<<endl; cout<<endl; cout<<"注意:使用本程序压缩后压缩文件拓展名为.gr。"<<endl; cout<<" 压缩和解压时请输入完整的文件路径。"<<endl; cout<<endl; cout<<"请选择操作:";}void select(HuffNode* HT, int i, int* s1, int* s2){ unsigned int j, s; s = 0; //记录当前找到的最小权值的结点的下标 for(j=1;j<=i;j++) { if(HT[j].parent == 0) //找最小 { if(s==0) //第一个找到的点 s=j; if(HT[j].count < HT[s].count) s=j; } } *s1 = s; s = 0; for(j=1;j<=i;j++) //找次小 { if((HT[j].parent == 0)&&(j!=*s1)) //仅比上面一个多了J!=*s1,应为不能是最小 { if(s==0) s=j; if(HT[j].count < HT[s].count) s=j; } } *s2 = s;}//创建的哈夫曼树是以一维数组建立,同时起始地址是1。int creatHuffmanTree(HuffNode* HT, Ascll* ascll){ int i,s1,s2,leafNum=0,j=0; //初始化叶节点,256个ascll字符 for(i = 0; i < 256; i ++) { //只使用出现的过的字符 ascll[i].count > 0 if(ascll[i].count > 0) { HT[++j].count = ascll[i].count; HT[j].alpha = ascll[i].alpha; HT[j].parent=HT[j].lchild=HT[j].rchild=0; } } // [叶子] [叶子] [叶子] [叶子] ···[内部] [根] leafNum = j; int nodeNum = 2*leafNum -1; //节点个数 //初始化内部节点 for(i = leafNum + 1; i <= nodeNum; i++) { HT[i].count = 0; HT[i].code[0] = 0; HT[i].parent = HT[i].lchild = HT[i].rchild = 0; } //给内部节点找孩子 for(i = leafNum + 1; i <= nodeNum; i++) { select(HT, i - 1, &s1, &s2); //找到当前最小和次小的树根 HT[s1].parent=i; HT[s2].parent=i; HT[i].lchild=s2; HT[i].rchild=s1; HT[i].count=HT[s1].count+HT[s2].count; } return leafNum;}//哈弗曼编码void HuffmanCoding(char* hTable[ASCLLNUM], HuffNode* HT, int leafNum){ int i,j,m,c,f,start; char cd[MAXLEN]; m = MAXLEN; cd[m-1] = 0; for(i=1;i <= leafNum;i++) { start = m-1; //先是从后往前编码,从子叶开始编码 for(c=i,f=HT[c].parent; f!=0; c=f,f=HT[f].parent) //找爸爸 { //判断自己c是爸爸的哪个孩子 if(HT[f].lchild==c) { // 左 0 cd[start--]='0'; } else { // 右 1 cd[start--]='1'; } } // [0 0 0 0 0 start 0 1 0 1 1], start 表示偏移,m-start 表示压入的01的长度,start到达根 start++; //int end = m-1; for(j=0;j<m-start;j++) { // 获取字符编码 HT[i].code[j]=cd[start+j]; // 编码 [叶子]---[根] //HT[i].code[j]=cd[end--]; } // 添加结尾 HT[i].code[j]='\0'; //写入字符-频数表 hTable[ HT[i].alpha ] = HT[i].code; }}void compress(bool compress){ FILE *infile = NULL, *outfile = NULL; char infileName[MAXLEN],outfileName[MAXLEN]; cout<<"\n请输入你想要压缩的文件路径:"; cin>>infileName; // 打开文件 infile = fopen(infileName,"rb"); while(infile == NULL) { cout<<"文件:"<<infileName<<"不存在..."<<endl; cout<<"重新输入需压缩文件路径(1)或返回主菜单(2)?"<<endl; char option; cin>>option; while(option != '1' && option != '2') { cout<<endl; cout<<"无效输入!"<<endl; cout<<"重新输入文件名(1)或返回主菜单(2)?"<<endl; cin>>option; } if(option == '2'){ return; } cout<<"\n请输入需压缩文件路径:"; cin>>infileName; // 读取文件 infile = fopen(infileName,"rb"); } // 创建文件名 strcpy(outfileName,infileName); strcat(outfileName,".gr"); // 判断文件是否存在 // 对文件进行操作,判断文件是否存在 while( (_access(outfileName, 0 )) != -1 ) { cout<<"文件:"<<outfileName<<"已存在..."<<endl; cout<<"是否替换原文件?(Y/N):"; char option; cin>>option; while(option != 'Y' && option != 'N' && option != 'y' && option != 'n') { cout<<"\n无效输入!"<<endl; cout<<"请输入Y或者N:"; cin>>option; } if(option == 'Y' || option == 'y'){ break; } cout<<"请手动输入压缩文件文件路径(含拓展名):"; cin>>outfileName; cout<<outfileName; //DEB } //判断是否可以创建该文件,如果不行,表示无法再文件系统中创建该文件。输入内容有误 outfile = fopen(outfileName,"wb"); if(outfile == NULL) { cout<<"\n无法创建该压缩文件..."<<endl; cout<<"请输入任意键返回主菜单..."; _getch(); return; } cout<<"文件压缩中..."<<endl; //[TIME-START] const double begin=(double)clock()/CLK_TCK; //统计字符种类数和频数 unsigned char c; int i,k; unsigned long total=0; //文件长度 // 利用hash表存放字母表及字母出现频数 Ascll ascll[ASCLLNUM]; for(i = 0; i < ASCLLNUM; i++) { ascll[i].count = 0; } while(!feof(infile)) { c=fgetc(infile); ascll[c].alpha = c; ascll[c].count++; total++; //读取到的字符个数 } total--;// ascll[c].count--; //TODO// 创建 哈弗曼树节点数组 HuffNode HT[MAXLEN]; int leafNum = creatHuffmanTree(HT,ascll); char *hTable[MAXLEN]; for(i = 0; i < ASCLLNUM; i ++) { hTable[i] = new char[MAXLEN]; }// 哈夫曼编码 HuffmanCoding(hTable, HT, leafNum); if(!compress){ cout<<"字母\t字频数表\t编码\t"<<endl; for(i = 0; i < 256; i ++){ if(ascll[i].count > 0){ cout<<ascll[i].alpha<<"\t"<<ascll[i].count<<"\t"<<hTable[ascll[i].alpha]<<endl; } } // 关闭打开的文件 fclose(infile); fclose(outfile); //[TIME-END] const double end=(double)clock()/CLK_TCK; cout<<"编码耗时:"<<(end-begin)<<" s"<<endl; return; }//写头文件 -- 将压缩的哈夫曼树编码信息写入哈夫曼树 fseek(infile,0,0); fwrite(&total,sizeof(unsigned long),1,outfile); //原文件总长度 for(i=0; i<=255; i++) { // 将哈夫曼树按照 unsigned long 的方式压入文件中,下标表示字母,数值表示字母的频数 fwrite(&ascll[i].count,sizeof(unsigned long),1,outfile); }//开始压缩主文件 //char buf[MAXLEN]; unsigned long j=0; //最大为total // buf[0]=0; string buf = "\0"; int charNum=2; while(!feof(infile)) { c=fgetc(infile); // cout<<c<<endl; // cout<<hTable[c]<<endl; string tempCode = hTable[c]; j++; // strcat(buf,tempCode); buf += tempCode; //k=strlen(buf); k = buf.length(); c=0; // 将所得的 0 1 每8个就可以构建一个字母的方式压入文件用 while(k>=8) { for(i=0; i<8; i++) { // 利用左移以为在右边空出一个空位 // 利用与 1 取或的方式压入 bit 1 if(buf[i]=='1') c=(c<<1)|1; else c=c<<1; } // cout<<c<<endl; fwrite(&c,sizeof(unsigned char),1,outfile); charNum ++; // strcpy(buf,buf+8); buf.erase(0,8); //k=strlen(buf); // 确定剩下的bit 的长度,如果大于8表示还可以压成一个字节 k = buf.length(); } if(j==total){ break; } } // 当 k < 8 时,表示还剩下不足 8 位的bit,需要拓展0位压缩 if(k>0) //可能还有剩余字符 { // strcat(buf,"00000000"); buf += "00000000"; for(i=0; i<8; i++) { if(buf[i]=='1') c=(c<<1)|1; else c=c<<1; } fwrite(&c,sizeof(unsigned char),1,outfile); charNum ++; }// 关闭打开的文件 fclose(infile); fclose(outfile); //[TIME-END] const double end=(double)clock()/CLK_TCK; cout<<"压缩成功!"<<endl; float s; s=(float)charNum / (float)total; cout<<"压缩率为:"<<s<<endl; cout<<"耗时为:"<<(end-begin)<<" s"<<endl; _getch(); return;}void decompress() { FILE *infile,*outfile; char infilename[255],outfilename[255]; cout<<"请输入要解压的文件的文件路径(不含.gr):"; cin>>outfilename; // 构建解压文件名 strcpy(infilename,outfilename); strcat(infilename,".gr"); infile = fopen(infilename,"rb"); //循环判断文件是否存在 while(infile==NULL){ char option; cout<<"文件"<<infilename<<"不存在...\n"; cout<<"重新输入文件名(1)或返回主菜单(2)?"; cin>>option; while(option!='1' && option!='2') { cout<<"\n无效的输入!\n"; cout<<"重新输入文件名(1)或返回主菜单(2)?"; cin>>option; } if(option == '2'){ return; } else { cout<<"\n请输入要解压的文件的文件路径(不含.gr):"; cin>>outfilename; // 构建解压文件名 strcpy(infilename,outfilename); strcat(infilename,".gr"); infile = fopen(infilename,"rb"); } } // 输入解压后的文件名 // cout<<"请输入解压后的文件的文件路径:"; // cin>>outfilename; outfile = fopen(outfilename,"wb"); if(outfile==NULL) { cout<<"\n解压文件失败!无法创建解压后的文件..."; cout<<"\n按任意键回到主菜单..."; _getch(); return; } cout<<"解压文件中..."<<endl; //[TIME-BEGIN] const double begin=(double)clock()/CLK_TCK; unsigned long total = 0; // 将第一个 long 长度数据读入 tatol 中,为文件的总大小 fread(&total,sizeof(unsigned long),1,infile); //cout<<"原来大小: "<<total<<endl; Ascll ascll[ASCLLNUM]; int i; for(i = 0; i < ASCLLNUM; i++) { // 之后的每个long长度都是一个字符的频数 fread(&ascll[i].count,sizeof(unsigned long),1,infile); ascll[i].alpha = i; } HuffNode HT[MAXLEN]; // 创建哈夫曼树 int leafNum = creatHuffmanTree(HT,ascll); //cout<<"leafNum = "<<leafNum<<endl; if(test){ for(i = 0; i < 256;i ++){ if(ascll[i].count > 0){ cout<<ascll[i].alpha<<"\t"<<ascll[i].count<<endl; } } }/* char *hTable[MAXLEN]; for(i = 0; i < MAXLEN; i ++) { hTable[i] = new char[MAXLEN]; }*/ fseek(infile,sizeof(unsigned long)*257,0); unsigned char c = 0; int index = 2*leafNum - 1; int charNum = 0; while(!feof(infile)) { // 按照字母读取 c=fgetc(infile); if(test){ cout<<"第一个字符:"<<c<<endl; } // 从根节点往叶子走,读取到的是一个 字母(8位), 所以使用循环8次 for(i=0; i<8; i++) { unsigned int cod = (c & 128); c = c << 1; if(cod == 0 ){ // 左 0 右 1 index = HT[index].lchild; } else{ index = HT[index].rchild; } if(HT[index].rchild == 0 && HT[index].lchild == 0){ charNum ++; // 到达叶子 char trueChar = HT[index].alpha; fwrite(&trueChar,sizeof(unsigned char),1,outfile); // index 重新指向根节点 index = 2*leafNum - 1; if(charNum >= total){ break; } } } if(charNum >= total){ break; } } // 关闭打开的文件 fclose(infile); fclose(outfile); //[TIME-END] const double end=(double)clock()/CLK_TCK; cout<<"解压成功"<<endl; float s; s=(float)charNum / (float)total; cout<<"完整度为:"<<s<<endl; cout<<"耗时为:"<<(end-begin)<<" s"<<endl; _getch(); return;}void shouEncode(){ compress(false); cout<<"已显示完成"<<endl; _getch();}void testZip(){ //[TIME-START] const double begin=(double)clock()/CLK_TCK; system("D:\\Install\\360Zip\\360Zip.exe D:\\Practice\\C++\\shanchu\\cacm.all.rar D:\\Practice\\C++\\shanchu\\cacm.all"); //[TIME-END] const double end=(double)clock()/CLK_TCK; cout<<"耗时:"<<(end - begin)<<endl; _getch();}int main(){ while(1){ showGUI(); char option; cin>>option; while(option!='1' && option!='2' && option!='3' && option!='4' && option!='5') { cout<<"无效的输入!\n"; cout<<"请选择操作:"; cin>>option; } switch(option){ case '1':{ compress(true); break; } case '2':{ decompress(); break; } case '3':{ shouEncode(); break; } case '4':{ testZip(); break; } default:{ cout<<"谢谢您的使用!"<<endl; return 0; } } system("cls"); }}
- 哈夫曼编码--压缩与解压
- 文件压缩与解压:哈夫曼编码
- 基于哈夫曼编码的任意文件压缩与解压
- 文件压缩与解压——哈夫曼编码
- 哈夫曼编码的一个实际应用(压缩与解压)
- 哈夫曼压缩与解压
- Huffman编码压缩算法之压缩与解压篇
- 基于哈夫曼编码的文件压缩解压
- C++ Huffman编码压缩解压
- 02. 哈夫曼算法与文本压缩、解压
- 文件压缩与解压
- java 压缩与解压
- tar 压缩与解压
- iPhone压缩与解压
- 压缩与解压命令
- 压缩与解压
- linux 压缩与解压
- ZIP压缩与解压
- 正则表达式
- 在页面加载后在设置embed 的src 怎么实现?
- oracle的分析函数over(Partition by...) 及开窗函数
- Java-检测Java程序运行时内存消耗的方法-Jconsole
- ORACLE 如何产生一个随机数
- 哈夫曼编码--压缩与解压
- 正则表达式
- servlet中forward与redirect的区别
- eclipse for mac部署web项目至本地的tomcat但在webapps中找不到的解决方法
- java,文件的绝对路径和相对路径
- C 语言流程控制与循环
- 闰秒原理及其对计算机系统影响
- Windows 高 DPI 的简单总结
- Android API Guide for Animation and Graphics(六)—— 动画与图形(OpenGL ES)