文件压缩

  1 #include <stdio.h>
  2 #include <string.h>
  3 #include <stdlib.h>
  4 #include <conio.h>
  5 #include <time.h>
  6 struct node{
  7     long weight;  //权值
  8     unsigned char ch;//字符
  9     int parent,lchild,rchild;
 10     char code[256];//编码的位数最多为256位
 11     int CodeLength;//编码长度
 12 }hfmnode[512];
 13 void compress();
 14 void uncompress(); 
 15 //主函数
 16 int main()
 17 {
 18     int choice;
 19      printf("请选择1~3:
");
 20      printf("1.压缩文件
");
 21      printf("2.解压文件
");
 22      printf("3.退出!
");
 23         scanf("%d",&choice);
 24         if(choice==1)compress();
 25         else if(choice==2)uncompress();
 26              else if(choice==3) return 0;
 27                   else printf("输入错误!");
 28     
 29 }
 30 
 31 //压缩函数
 32     void compress()
 33     {
 34         int i,j;
 35         char infile[20],outfile[20];
 36         FILE  *ifp,*ofp; 
 37         unsigned char c;//
 38         long FileLength,filelength=0;
 39         int n,m;//叶子数和结点数
 40         int s1,s2; //权值最小的两个结点的标号
 41         char codes[256];
 42         long sumlength=0;
 43         float rate,speed;
 44         int count=0;
 45         clock_t start1, start2,finish1,finish2;
 46         double  duration1,duration2;
 47     void encode(struct node *nodep,int n);//编码函数
 48     int select(struct node *nodep,int pose);//用于建哈弗曼树中选择权值最小的结点的函数
 49         printf("请输入要压缩的文件名:");
 50         scanf("%s",infile);
 51         ifp=fopen(infile,"rb");
 52         if(ifp==NULL)
 53         {
 54             printf("文件名输入错误,文件不存在!
");
 55             return;
 56         }
 57         printf("请输入目标文件名:");
 58           scanf("%s",outfile);
 59         ofp=fopen(outfile,"wb");
 60         if(ofp==NULL)
 61         {
 62            printf("文件名输入错误,文件不存在!
");
 63             return;
 64         }
 65 
 66 start1=clock() ;//开始计时1
 67 
 68     //统计文件中字符的种类以及各类字符的个数
 69     //先用字符的ASCII码值代替结点下标
 70         FileLength=0;
 71         while(!feof(ifp)) 
 72         {
 73          fread(&c,1,1,ifp);
 74          hfmnode[c].weight++; 
 75          FileLength++;
 76         }
 77         FileLength--; //文件中最后一个字符的个数会多统计一次,所以要减一
 78         hfmnode[c].weight--;
 79         //再将ASCII转换为字符存入到结点的ch成员里,同时给双亲、孩子赋初值-1
 80             n=0;
 81             for(i=0;i<256;i++)
 82                 if(hfmnode[i].weight!=0)
 83                 {
 84                     hfmnode[i].ch=(unsigned char)i;
 85                       n++;//叶子数
 86                 hfmnode[i].lchild=hfmnode[i].rchild=hfmnode[i].parent=-1;        
 87                 } 
 88 m=2*n-1;//哈弗曼树结点总数
 89  j=0;
 90  for(i=0;i<256;i++)//去掉权值为0的结点
 91      if(hfmnode[i].weight!=0)
 92      {
 93         hfmnode[j]=hfmnode[i];
 94         j++;
 95      }
 96 
 97 for(i=n;i<m;i++)//初始化根结点
 98 {
 99         hfmnode[i].lchild=hfmnode[i].rchild=-1;
100         hfmnode[i].parent=-1;
101 }
102 //建立哈弗曼树
103    for(i=n;i<m;i++)
104    {
105           s1=select(hfmnode,i-1);
106         hfmnode[i].lchild=s1;
107         hfmnode[s1].parent=i;
108              s2=select(hfmnode,i-1);
109         hfmnode[i].rchild=s2;
110         hfmnode[s2].parent=i;
111       hfmnode[i].weight=hfmnode[s1].weight+hfmnode[s2].weight;
112    }
113 //编码
114 encode(hfmnode,n);
115 
116  finish1=clock();
117 duration1=(double)(finish1- start1) / CLOCKS_PER_SEC;
118  /*printf( "哈弗曼树编码用时为:%f seconds
", duration1 );*/
119   printf("编码完成,是否查看编码信息: y or n?
");
120         c=getch();
121         if(c=='y')
122         {    printf("
");
123             printf("叶子数为%d,结点数为%d
",n,m); 
124             for(i=0;i<n;i++)
125             printf("%d号叶子结点的权值为:%ld,双亲为:%d,左右孩子:%d,编码为:%s
",
126                 i,hfmnode[i].weight,hfmnode[i].parent,hfmnode[i].lchild,hfmnode[i].code);
127         }    
128 start2=clock() ;//开始计时2
129      fseek(ifp,0,SEEK_SET);//将ifp指针移到文件开头位置
130      fwrite(&FileLength,4,1,ofp);//将FileLength写入目标文件的前4个字节的位置
131      fseek(ofp,8,SEEK_SET);//再将目标文件指针ofp移到距文件开头8个字节位置
132    codes[0]=0;
133     //将编码信息写入目标文件
134      while(!feof(ifp))
135      {
136          fread(&c,1,1,ifp);
137          filelength++;
138          for(i=0;i<n;i++)
139            if(c==hfmnode[i].ch) break;    //ch必须也为unsigned 型
140          strcat(codes,hfmnode[i].code);
141           while(strlen(codes)>=8)
142           {
143               for(i=0;i<8;i++)//将codes的前8位01代码表示的字符存入c
144               {
145                   if(codes[i]=='1')
146                       c=(c<<1)|1;
147                   else c=c<<1;
148              }
149             fwrite(&c,1,1,ofp); //将新的字符写入目标文件
150             sumlength++;
151             strcpy(codes,codes+8);//更新codes的值
152           }
153        if(filelength==FileLength) break;
154      }
155 
156        //再将剩余的不足8位的01代码补全8位,继续写入
157        if(strlen(codes)>0)
158        {
159            strcat(codes,"00000000");
160            for(i=0;i<8;i++)
161            {
162               if(codes[i]=='1') 
163                   c=(c<<1)|1;
164               else c=c<<1;
165            }
166            fwrite(&c,1,1,ofp);
167            sumlength++;
168        }
169      
170        sumlength+=8;
171 printf("编码区总长为:%ld个字节
",sumlength-8);
172      //将sumlength和n的值写入目标文件,为的是方便解压
173         fseek(ofp,4,SEEK_SET);
174         fwrite(&sumlength,4,1,ofp);//把sumlength写进目标文件的第5-8个字节里         
175         fseek(ofp,sumlength,SEEK_SET);
176         fwrite(&n,4,1,ofp);//把叶子数n写进编码段后面的4个字节的位置
177     //为方便解压,把编码信息存入n后面的位置
178     //存储方式为:n*(字符值(1个字节)+该字符的01编码的位数(1个字节)+编码(字节数不确定,用count来计算总值))
179         for(i=0;i<n;i++)
180         {
181            fwrite(&(hfmnode[i].ch),1,1,ofp);
182            c=hfmnode[i].CodeLength;//编码最长为256位,因此只需用一个字节存储
183            fwrite(&c,1,1,ofp);
184            //写入字符的编码
185            if(hfmnode[i].CodeLength%8!=0) 
186               for(j=hfmnode[i].CodeLength%8;j<8;j++)//把编码不足8位的在低位补0,赋值给C,再把C写入
187               strcat(hfmnode[i].code,"0");
188            while(hfmnode[i].code[0]!=0)//开始存入编码,每8位二进制数存入一个字节
189            {
190                c=0;
191                for(j=0;j<8;j++)
192                {
193                    if(hfmnode[i].code[j]=='1')
194                         c=(c<<1)|1;
195                    else c=c<<1;
196                }
197                strcpy(hfmnode[i].code,hfmnode[i].code+8);//编码前移8位,继续存入编码
198                count++; //编码占的字节数的总值
199                fwrite(&c,1,1,ofp);
200            }
201         }
202         printf("
");
203          finish2=clock();
204          duration2=(double)(finish2- start2) / CLOCKS_PER_SEC;
205          /*printf( "写入目标文件用时为:%f seconds
", duration2);*/
206          printf( "压缩用时为:%f seconds
", duration1+duration2);
207            speed=(float)FileLength/(duration1+duration2)/1000;
208            printf("
压缩速率为:%5.2f KB/S
",speed);
209         printf("
");
210         printf("源文件长度为:%ld个字节
",FileLength);
211         sumlength=sumlength+4+n*2+count; //计算压缩后文件的长度
212         printf("压缩后文件长度为:%ld个字节
",sumlength);
213         rate=(float)sumlength/(float)FileLength;
214         printf("压缩率(百分比)为:%4.2f%%%
",rate*100);
215         fclose(ifp);
216         fclose(ofp);  
217         return;    
218 }
219 //返回书签
220 
221 //建立哈弗曼树中用于选择最小权值结点的函数
222 int select(struct node *nodep,int pose)
223 { 
224     int i;
225     int s1;
226     long min=2147483647;//s初值为long型的最大值
227     for(i=0;i<=pose;i++)
228     {
229         if(nodep[i].parent!=-1)continue;
230         if(nodep[i].weight<min)
231         {
232             min=nodep[i].weight;  
233             s1=i;
234         }
235     }
236     return s1;
237 }
238 //返回书签
239 
240 //哈弗曼编码函数
241 void encode(struct node *nodep,int n)
242 {   //从叶子向根求每个字符的哈弗曼编码
243     int start;
244     int i,f,c;
245     char codes[256];
246     codes[n-1]='';  //编码结束符
247     for(i=0;i<n;i++) //逐个字符求哈弗曼编码
248     {
249        start=n-1;
250        for(c=i,f=nodep[i].parent;f!=-1;c=f,f=nodep[f].parent)
251        {
252            start--;
253            if(nodep[f].lchild==c)
254                codes[start]='0';
255            else codes[start]='1';
256            
257        }
258        strcpy(nodep[i].code,&codes[start]);
259        nodep[i].CodeLength=strlen(nodep[i].code);  
260     }
261 }
262 //返回书签
263 
264 //解压函数
265 void uncompress() //解压文件
266 
267 { 
268     clock_t start, finish;
269     double  duration;
270     FILE *ifp,*ofp;  
271     char infile[20],outfile[20];
272     long FileLength,sumlength,filelength;
273     int n,m;  
274     int i,j,k;
275     char buf[256],codes[256];
276     unsigned char c;
277     int maxlength;
278     float speed;
279      printf("请输入要解压的文件名:");
280         scanf("%s",infile);
281         ifp=fopen(infile,"rb");
282         if(ifp==NULL)
283         {
284         printf("文件名输入错误,文件不存在!
");
285         return;
286         }
287      printf("请输入目标文件名:");
288           scanf("%s",outfile);
289         ofp=fopen(outfile,"wb");
290         if(ofp==NULL)
291         {
292         printf("文件名输入错误,文件不存在!
");
293         return;
294         }
295 start=clock() ;//开始计时
296     fread(&FileLength,4,1,ifp);//从压缩文件读出FileLength、sumlength
297     fread(&sumlength,4,1,ifp);
298     fseek(ifp,sumlength,SEEK_SET);    //利用sumlength读出n的值
299     fread(&n,4,1,ifp);
300     printf("
解码信息:源文件长度为%d个字节,字符种类n=%d
",FileLength,n);
301     for(i=0;i<n;i++)//读结点信息
302     {
303        fread(&hfmnode[i].ch,1,1,ifp);//字符
304        fread(&c,1,1,ifp);//编码长度
305        hfmnode[i].CodeLength=c;
306        hfmnode[i].code[0]=0;
307        if(hfmnode[i].CodeLength%8>0) m=hfmnode[i].CodeLength/8+1;//m为编码占的字节数
308        else m=hfmnode[i].CodeLength/8;
309        for(j=0;j<m;j++)//根据字节长度m读出编码
310        {
311            fread(&c,1,1,ifp);//此处c为01编码转换成的字符
312            itoa(c,buf,2);//字符型编码转换成二进制型(首位为1)
313           //如果编码不够8位,则说明缺少了8-k位0,因此应先在前面空缺位写0
314            for(k=8;k>strlen(buf);k--)
315            {
316                strcat(hfmnode[i].code,"0");
317            }
318            //再把二进制编码存进hfmnode.code中
319             strcat(hfmnode[i].code,buf);
320        }
321        hfmnode[i].code[hfmnode[i].CodeLength]=0;//去掉编码中多余的0  
322     }
323  //找出编码长度的最大值
324  maxlength=0;
325  for(i=0;i<n;i++)
326      if(hfmnode[i].CodeLength>maxlength)
327          maxlength=hfmnode[i].CodeLength;
328 //开始写入目标文件
329  fseek(ifp,8,SEEK_SET); //指针指向编码区,开始解码
330  filelength=0;
331  codes[0]=0;
332  buf[0]=0;
333  while(1)
334  {
335     while(strlen(codes)<maxlength)//codes小于编码长度的最大值时,继续读码
336     {
337         fread(&c,1,1,ifp);
338         itoa(c,buf,2);//还原编码
339         for(k=8;k>strlen(buf);k--)
340         {
341            strcat(codes,"0");//把缺掉的0补上
342         }
343         strcat(codes,buf);//codes中此时存的为一串01编码
344     }
345     for(i=0;i<n;i++)
346     {  //在codes中查找能使其前weight位和hfmnode.code相同的i值,weight即为codelength
347       if(memcmp(hfmnode[i].code,codes,(unsigned int)hfmnode[i].CodeLength)==0) break;
348     }
349     strcpy(codes,codes+hfmnode[i].CodeLength);//更新codes的值
350     c=hfmnode[i].ch;
351     fwrite(&c,1,1,ofp);
352 
353     filelength++;
354     if(filelength==FileLength) break;//写入结束
355  }
356      finish = clock();
357      duration = (double)(finish - start) / CLOCKS_PER_SEC;
358      printf( "
解压完成,解压用时为:%f seconds
", duration );
359        
360      fseek(ifp,0,SEEK_SET);  
361      FileLength=0;
362         while(!feof(ifp)) 
363         {
364           fread(&c,1,1,ifp);
365           FileLength++;
366         }
367         FileLength--; 
368     speed=(float)FileLength/duration/1000;
369     /*printf("此文件长度为:%ld个字节
",FileLength);*/
370     printf("
解压速度为:%5.2fKB/S
",speed);
371 
372    fclose(ifp);
373    fclose(ofp);
374    return;
375 }

效果图:

原文地址:https://www.cnblogs.com/joyeehe/p/8078963.html