哈夫曼实现文件压缩解压缩(c语言)

举报
兔老大 发表于 2021/04/26 00:55:46 2021/04/26
【摘要】 写一个对文件进行压缩和解压缩的程序,功能如下: ① 可以对纯英文文档实现压缩和解压; ② 较好的界面程序运行的说明。     介绍哈夫曼:   效率最高的判别树即为哈夫曼树 在计算机数据处理中,霍夫曼编码使用变长编码表对源符号(如文件中的一个字母)进行编码,其中变长编码表是通过一种评估来源符号出现机率的方法得到的,出现机率...

写一个对文件进行压缩和解压缩的程序,功能如下:

① 可以对纯英文文档实现压缩和解压;

② 较好的界面程序运行的说明。

 

 

介绍哈夫曼:

 

效率最高的判别树即为哈夫曼树

在计算机数据处理中,霍夫曼编码使用变长编码表对源符号(如文件中的一个字母)进行编码,其中变长编码表是通过一种评估来源符号出现机率的方法得到的,出现机率高的字母使用较短的编码,反之出现机率低的则使用较长的编码,这便使编码之后的字符串的平均长度、期望值降低,从而达到无损压缩数据的目的。

例如,在英文中,e的出现机率最高,而z的出现概率则最低。当利用霍夫曼编码对一篇英文进行压缩时,e极有可能用一个比特来表示,而z则可能花去25个比特(不是26)。用普通的表示方法时,每个英文字母均占用一个字节,即8个比特。二者相比,e使用了一般编码的1/8的长度,z则使用了3倍多。倘若我们能实现对于英文中各个字母出现概率的较准确的估算,就可以大幅度提高无损压缩的比例。

霍夫曼树又称最优二叉树,是一种带权路径长度最短的二叉树。所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的路径长度(若根结点为0层,叶结点到根结点的路径长度为叶结点的层数)。树的路径长度是从树根到每一结点的路径长度之和,记为WPL=(W1*L1+W2*L2+W3*L3+...+Wn*Ln),N个权值Wi(i=1,2,...n)构成一棵有N个叶结点的二叉树,相应的叶结点的路径长度为Li(i=1,2,...n)。可以证明霍夫曼树的WPL是最小的。

 

文件压缩与解压

姓名:  范天祚 

1 程序说明

1.1数据结构

哈夫曼树

1.2函数功能说明

printfPercent界面

compress()读取文件内容并加以压缩,将压缩内容写入另一个文档

uncompress()解压缩文件,并将解压后的内容写入新文件

1.3 程序编写的思路及流程

压缩:统计字符出现次数、将节点按出现次数排序、构造哈夫曼树、设置字符编码、读文件字符、按设置好的编码替换字符、写入存储文件

解压:读取文件各参数、转换成二进制码、按码求对应字符、写入存储文件


  
  1. #define _CRT_SECURE_NO_WARNINGS
  2. #include <stdlib.h>
  3. #include <stdio.h>
  4. #include <string.h>
  5. struct head
  6. {
  7. int b; //字符
  8. long count; //文件中该字符出现的次数
  9. long parent, lch, rch; //make a tree
  10. char bits[256]; //the huffuman code
  11. };
  12. struct head header[512], tmp; //节点树
  13. void printfPercent(int per)
  14. {
  15. int i = 0;
  16. printf("|");
  17. for(i = 0; i < 10; i++)
  18. {
  19. if(i < per/10)
  20. printf(">");
  21. else
  22. printf("-");
  23. }
  24. printf("|已完成%d%%\n",per);
  25. }
  26. //函数:compress()
  27. //作用:读取文件内容并加以压缩
  28. //将压缩内容写入另一个文档
  29. int compress(const char *filename,const char *outputfile)
  30. {
  31. char buf[512];
  32. unsigned char c;
  33. long i, j, m, n, f;
  34. long min1, pt1, flength;
  35. FILE *ifp, *ofp;
  36. int per = 10;
  37. ifp = fopen(filename, "rb"); //打开原始文件
  38. if (ifp == NULL)
  39. {
  40. printf("打开文件失败:%s\n",filename);
  41. return 0; //如果打开失败,则输出错误信息
  42. }
  43. ofp = fopen(outputfile,"wb"); //打开压缩后存储信息的文件
  44. if (ofp == NULL)
  45. {
  46. printf("打开文件失败:%s\n",outputfile);
  47. return 0;
  48. }
  49. flength = 0;
  50. while (!feof(ifp))
  51. {
  52. fread(&c, 1, 1, ifp);
  53. header[c].count ++; //读文件,统计字符出现次数
  54. flength ++; //记录文件的字符总数
  55. }
  56. flength --;
  57. header[c].count --;
  58. for (i = 0; i < 512; i ++) //HUFFMAN算法中初始节点的设置
  59. {
  60. if (header[i].count != 0)
  61. header[i].b = (unsigned char) i;
  62. else
  63. header[i].b = -1;
  64. header[i].parent = -1;
  65. header[i].lch = header[i].rch = -1;
  66. }
  67. for (i = 0; i < 256; i ++) //将节点按出现次数排序
  68. {
  69. for (j = i + 1; j < 256; j ++)
  70. {
  71. if (header[i].count < header[j].count)
  72. {
  73. tmp = header[i];
  74. header[i] = header[j];
  75. header[j] = tmp;
  76. }
  77. }
  78. }
  79. for (i = 0; i < 256; i ++) //统计不同字符的数量
  80. {
  81. if (header[i].count == 0)
  82. break;
  83. }
  84. n = i;
  85. m = 2 * n - 1;
  86. for (i = n; i < m; i ++)
  87. {
  88. min1 = 999999999;
  89. for (j = 0; j < i; j ++)
  90. {
  91. if (header[j].parent != -1) continue;
  92. if (min1 > header[j].count)
  93. {
  94. pt1 = j;
  95. min1 = header[j].count;
  96. continue;
  97. }
  98. }
  99. header[i].count = header[pt1].count;
  100. header[pt1].parent = i;
  101. header[i].lch = pt1;
  102. min1 = 999999999;
  103. for (j = 0; j < i; j ++)
  104. {
  105. if (header[j].parent != -1) continue;
  106. if (min1 > header[j].count)
  107. {
  108. pt1 = j;
  109. min1 = header[j].count;
  110. continue;
  111. }
  112. }
  113. header[i].count += header[pt1].count;
  114. header[i].rch = pt1;
  115. header[pt1].parent = i;
  116. }
  117. for (i = 0; i < n; i ++) //构造HUFFMAN树,设置字符的编码
  118. {
  119. f = i;
  120. header[i].bits[0] = 0;
  121. while (header[f].parent != -1)
  122. {
  123. j = f;
  124. f = header[f].parent;
  125. if (header[f].lch == j)
  126. {
  127. j = strlen(header[i].bits);
  128. memmove(header[i].bits + 1, header[i].bits, j + 1);
  129. header[i].bits[0] = '0';
  130. }
  131. else
  132. {
  133. j = strlen(header[i].bits);
  134. memmove(header[i].bits + 1, header[i].bits, j + 1);
  135. header[i].bits[0] = '1';
  136. }
  137. }
  138. }
  139. //下面的就是读原文件的每一个字符,按照设置好的编码替换文件中的字符
  140. fseek(ifp, 0, SEEK_SET); //将指针定在文件起始位置
  141. fseek(ofp, 8, SEEK_SET); //以8位二进制数为单位进行读取
  142. buf[0] = 0;
  143. f = 0;
  144. pt1 = 8;
  145. printf("读取将要压缩的文件:%s\n",filename);
  146. printf("当前文件有:%d字符\n",flength);
  147. printf("正在压缩\n");
  148. while (!feof(ifp))
  149. {
  150. c = fgetc(ifp);
  151. f ++;
  152. for (i = 0; i < n; i ++)
  153. {
  154. if (c == header[i].b) break;
  155. }
  156. strcat(buf, header[i].bits);
  157. j = strlen(buf);
  158. c = 0;
  159. while (j >= 8) //当剩余字符数量不小于8个时
  160. {
  161. for (i = 0; i < 8; i ++) //按照八位二进制数转化成十进制ASCII码写入文件一次进行压缩
  162. {
  163. if (buf[i] == '1') c = (c << 1) | 1;
  164. else c = c << 1;
  165. }
  166. fwrite(&c, 1, 1, ofp);
  167. pt1 ++;
  168. strcpy(buf, buf + 8);
  169. j = strlen(buf);
  170. }
  171. if(100 * f/flength > per)
  172. {
  173. printfPercent(per);
  174. per += 10;
  175. }
  176. if (f == flength)
  177. break;
  178. }
  179. printfPercent(100);
  180. if (j > 0) //当剩余字符数量少于8个时
  181. {
  182. strcat(buf, "00000000");
  183. for (i = 0; i < 8; i ++)
  184. {
  185. if (buf[i] == '1') c = (c << 1) | 1;
  186. else c = c << 1; //对不足的位数进行补零
  187. }
  188. fwrite(&c, 1, 1, ofp);
  189. pt1 ++;
  190. }
  191. fseek(ofp, 0, SEEK_SET); //将编码信息写入存储文件
  192. fwrite(&flength,1,sizeof(flength),ofp);
  193. fwrite(&pt1, sizeof(long), 1, ofp);
  194. fseek(ofp, pt1, SEEK_SET);
  195. fwrite(&n, sizeof(long), 1, ofp);
  196. for (i = 0; i < n; i ++)
  197. {
  198. tmp = header[i];
  199. fwrite(&(header[i].b), 1, 1, ofp);
  200. pt1++;
  201. c = strlen(header[i].bits);
  202. fwrite(&c, 1, 1, ofp);
  203. pt1++;
  204. j = strlen(header[i].bits);
  205. if (j % 8 != 0) //当位数不满8时,对该数进行补零操作
  206. {
  207. for (f = j % 8; f < 8; f ++)
  208. strcat(header[i].bits, "0");
  209. }
  210. while (header[i].bits[0] != 0)
  211. {
  212. c = 0;
  213. for (j = 0; j < 8; j ++)
  214. {
  215. if (header[i].bits[j] == '1') c = (c << 1) | 1;
  216. else c = c << 1;
  217. }
  218. strcpy(header[i].bits, header[i].bits + 8);
  219. fwrite(&c, 1, 1, ofp); //将所得的编码信息写入文件
  220. pt1++;
  221. }
  222. header[i] = tmp;
  223. }
  224. fclose(ifp);
  225. fclose(ofp); //关闭文件
  226. printf("压缩后文件为:%s\n",outputfile);
  227. printf("压缩后文件有:%d字符\n",pt1 + 4);
  228. return 1; //返回压缩成功信息
  229. }
  230. //函数:uncompress()
  231. //作用:解压缩文件,并将解压后的内容写入新文件
  232. int uncompress(const char *filename,const char *outputfile)
  233. {
  234. char buf[255], bx[255];
  235. unsigned char c;
  236. char out_filename[512];
  237. long i, j, m, n, f, p, l;
  238. long flength;
  239. int per = 10;
  240. int len = 0;
  241. FILE *ifp, *ofp;
  242. char c_name[512] = {0};
  243. ifp = fopen(filename, "rb"); //打开文件
  244. if (ifp == NULL)
  245. {
  246. return 0; //若打开失败,则输出错误信息
  247. }
  248. //读取原文件长
  249. if(outputfile)
  250. strcpy(out_filename,outputfile);
  251. else
  252. strcpy(out_filename,c_name);
  253. ofp = fopen(out_filename, "wb"); //打开文件
  254. if (ofp == NULL)
  255. {
  256. return 0;
  257. }
  258. fseek(ifp,0,SEEK_END);
  259. len = ftell(ifp);
  260. fseek(ifp,0,SEEK_SET);
  261. printf("将要读取解压的文件:%s\n",filename);
  262. printf("当前文件有:%d字符\n",len);
  263. printf("正在解压\n");
  264. fread(&flength, sizeof(long), 1, ifp); //读取原文件长
  265. fread(&f, sizeof(long), 1, ifp);
  266. fseek(ifp, f, SEEK_SET);
  267. fread(&n, sizeof(long), 1, ifp); //读取原文件各参数
  268. for (i = 0; i < n; i ++) //读取压缩文件内容并转换成二进制码
  269. {
  270. fread(&header[i].b, 1, 1, ifp);
  271. fread(&c, 1, 1, ifp);
  272. p = (long) c;
  273. header[i].count = p;
  274. header[i].bits[0] = 0;
  275. if (p % 8 > 0) m = p / 8 + 1;
  276. else m = p / 8;
  277. for (j = 0; j < m; j ++)
  278. {
  279. fread(&c, 1 , 1 , ifp);
  280. f = c;
  281. _itoa(f, buf, 2);
  282. f = strlen(buf);
  283. for (l = 8; l > f; l --)
  284. {
  285. strcat(header[i].bits, "0"); //位数不足,执行补零操作
  286. }
  287. strcat(header[i].bits, buf);
  288. }
  289. header[i].bits[p] = 0;
  290. }
  291. for (i = 0; i < n; i ++)
  292. {
  293. for (j = i + 1; j < n; j ++)
  294. {
  295. if (strlen(header[i].bits) > strlen(header[j].bits))
  296. {
  297. tmp = header[i];
  298. header[i] = header[j];
  299. header[j] = tmp;
  300. }
  301. }
  302. }
  303. p = strlen(header[n-1].bits);
  304. fseek(ifp, 8, SEEK_SET);
  305. m = 0;
  306. bx[0] = 0;
  307. while (1)
  308. {
  309. while (strlen(bx) < (unsigned int)p)
  310. {
  311. fread(&c, 1, 1, ifp);
  312. f = c;
  313. _itoa(f, buf, 2);
  314. f = strlen(buf);
  315. for (l = 8; l > f; l --)
  316. {
  317. strcat(bx, "0");
  318. }
  319. strcat(bx, buf);
  320. }
  321. for (i = 0; i < n; i ++)
  322. {
  323. if (memcmp(header[i].bits, bx, header[i].count) == 0) break;
  324. }
  325. strcpy(bx, bx + header[i].count);
  326. c = header[i].b;
  327. fwrite(&c, 1, 1, ofp);
  328. m ++;
  329. if(100 * m/flength > per)
  330. {
  331. printfPercent(per);
  332. per += 10;
  333. }
  334. if (m == flength) break;
  335. }
  336. printfPercent(100);
  337. fclose(ifp);
  338. fclose(ofp);
  339. printf("解压后文件为:%s\n",out_filename);
  340. printf("解压后文件有:%d字符\n",flength);
  341. return 1; //输出成功信息
  342. }
  343. int main(int argc,const char *argv[])
  344. {
  345. memset(&header,0,sizeof(header));
  346. memset(&tmp,0,sizeof(tmp));
  347. compress("测试文档.txt","测试文档.txt.zip");
  348. uncompress("测试文档.txt.zip","测试文档.txt 解压后.txt");
  349. system("pause");
  350. return 0;
  351. }

 

2 功能展示

2.1 控制台显示

2.2 文件效果

开始时只有一个文件《测试文档.txt》:

打开《测试文档.txt》

《测试文档.txt》文件大小:

程序运行结束后多了两个文件:

以文本形式打开压缩二进制文件《测试文档.txt.zip》:

《测试文档.txt.zip》文件属性:

文章来源: fantianzuo.blog.csdn.net,作者:兔老大RabbitMQ,版权归原作者所有,如需转载,请联系作者。

原文链接:fantianzuo.blog.csdn.net/article/details/86613332

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。