利用哈夫曼编码压缩解压文件
1. 引言
本文为大一下学期C语言课程的期末大作业,经过修改后发布。文中要用到的测试文件1.lst见链接: https://pan.baidu.com/s/1hs7XoIK 密码: wpru。
编译环境:Ubuntu 16.04
本文主要考核如何以C实现集成电路测试向量文件的无损压缩。在通常的文件存储中,无论是二进制格式的文件还是文本文件,几乎都是等宽的编码。比如ASCII码格式的文本文件,每个字符由一个ASCII码表示,宽度为8bit。然而,从磁盘空间利用率的角度看,这并不是一种效率最高的存储方案。为了理解定长编码与变长编码的区别,假设某个文件纯粹由abcdef共6个字符组成,作为定长编码,至少需要3bit才能表示6个不同的字符,假设每个字符出现的频率以及两种编码方案如下表所示
下面我们计算一下上述两种编码方案的总编码长度。由于6个字符共出现了 100K次,对于定长编码,每个字符占用3bit,总共将占用300Kb的空间;对于变长编码,总计的编码长度为:45*1+(13+12+16)*3+(9+5)*4=224Kb,节省了76Kb的空间。这也正是本次大作业的基本的实现原理。上表中的变长编码有个特点,就是出现越频繁的字符,用越短的编码去表示;而且,每个字符的编码都不是另一个字符编码的前缀,从而简化了解码时的难度,因此又被称为前缀编码。哈夫曼编码就是这样一种编码。本作业要求以哈夫曼编码的原理,编写一个可以将给定的文件进行文件压缩,同时可以将压缩的文件进行解压的程序。比如假设编写的程序为huff,huff -c test.dat –o test.huff将test.dat文件压缩为test.huff;类似的,huff -d test.huff -o test.dat将压缩文件进行解压为原始的test.dat。现在问题的主要难点在于如何针对给定的文件由程序自己统计不同字符出现的频率并据此构造哈夫曼编码。
哈夫曼编码原理
上面表格中所示。 哈夫曼编码树的构造过程如下图所示。
首先,创建 5 个叶子节点,如上图(a)所示,冒号后面是该节点字符出现的频率;接下来每次从当前结点中寻找两个出现频度最低的节点,出现频率最低的放在左边,如(b)的f,次低的放右边,如上图(b)的 e,然后将这两个出现频率最低的节点合并为一个节点,以两个节点出现的总频率为名字,如上图(b)的 14 节点, 14 节点和 f 与 e 分别用标记为 0/1的边连接,这时 f 和 e 两个节点已被删除并被合并为一个 14 节点,因此总的节点数少了一个;接下来继续寻找两个频率最低的节点,如上图(c)中的 c 和 b,二者合并为 25 节点,分别与 c 和 b 以边 0/1 连接;接下来 14 和 d 节点出现频率最低,分别为 14 和 16,合并为 30节点;继续此过程, 25 和 30 节点合并为 55 节点, 55 节点和 a 合并为最终的 100 节点。至此哈夫曼编码树构造完成,如上图(f)。观察上图(f),所有表示真实字符的节点都放置于矩形框中,且都是叶子节点。从根节点到叶子节点的边的标签相连就是该叶子节点的编码,即 a: 0; b: 101; c: 100; d: 111; e: 1101;f: 1100,恰好是前面表中的变长编码方案。
2. 基本原理/实现方法
以下是几个主要部分的编码解释,大家可以直接跳过,文件源代码贴在最后。
由试验简介,根据哈夫曼编码的原理,可以实现文件的压缩与解压。压缩时读取字符转化而相应的哈夫曼二进制编码,解压即为其逆操作。接下来对完成的程序进行分析,具体的代码见huffman.c。
【程序及算法分析】
a) 哈夫曼树建立预备步骤
void initialize(struct node *HuffmanTreeNodeTemp,int count[]) 函数
/***********压缩文件