实验四、无损数据压缩编解码实验
一、概述
本次实验要求大家掌握霍夫曼编解码实现的数据结构以及具体的实现方法,并在实现的基础之上分析对不同文件进行压缩的效率。
二、实验涉及到基本原理
1.涉及原理汇总
本次实验具体设计的原理有:霍夫曼编码的原理,二叉树与其节点的数据结构,以及对编码效率的分析。
2.霍夫曼编码的原理
霍夫曼编码充分利用了信源概率分布的特性进行编码,是一种最佳的逐个符号的编码方法。
(1)首先将q个信源符号按概率从大到小以递减的次序排列;
(2)用0和1码符号分别分配给概率最小的两个信源符号,并将这两个信源符号合成为一个新的信源符号,从而获得一个新的q-1个规模的信源;
(3)重复上述过程,迭代至信源规模变为1且概率之和为1;
(4)从最后一个节点开始,沿编码路径向前返回,就能得出各信源符号所对应的码字。
3.二叉树的遍历
为了实现霍夫曼编码,本次的程序中使用了二叉树这样的数据结构来生成码树。二叉树这种数据结构不同于我们常见的数组,它并不是纯粹的线性结构,其中的节点并不存在天然的前驱或后继。但在一定约束条件下,例如遍历,我们也可以在树中确立某种线性的次序。
对二叉树进行遍历本身便有多种方式,例如先序遍历,中序遍历与后序遍历等。它们间的区别虽仅是根节点的遍历先后顺序有不同,但这也足以产生相当大的差异了。当然,除这三种遍历方式之外还有更多更加复杂的遍历方式。
本次实验中的霍夫曼编码程序采用的是先序遍历。
4.编码效率的分析
我们本次实验对编码效率的分析从两个角度出发。首先是从理论角度出发,通过计算信源熵以及平均码长来衡量编码的效率;而另一个角度则是直接直观的比较文件的大小,这一种方法相较与单纯从理论角度分析更有现实意义,因为实际的应用中码表本身也是会影响文件大小的。
三、实验流程
首先,调试霍夫曼编码程序。调试成功后,向程序中添加功能,使程序可以以txt文本文档的格式输出,字符发生的概率,字符对应的码字的编码码长以及字符对应的码字。完成该功能后,对不同格式的文件进行霍夫曼编码,并从统计特性以及压缩效率的角度进行分析。
四、关键代码及分析
1.关键代码概述:
本部分内容将率先分析本程序中出现的重要的几个结构体,并着重分析霍夫曼编码器的实现,最后简单介绍一下添加的功能。
2.关键的结构体:
(1)huffman_node
该结构体是本程序中霍夫曼码树的节点的结构体。
isLeaf是用于将节点区分为叶节点和非叶节点(1为叶节点,0为非叶节点),这可以给之后的遍历带来很大的方便。
count则是用于存放该节点所对应的信源符号集合中信源符号出现的个数。
*parent则是该节点的父节点的指针,*zero与*one分别对应着左孩子节点和右孩子节点(习惯上约定左孩子要优先于右孩子,实际上两者地位相当)。
若其为叶节点,symbol内会记录其信源符号。
值得注意的地方是isLeaf和共用体union的部分。由于叶节点是没有子节点的,故共用体的空间由symbol占用;而非叶节点是没有对应信源符号的故,共用体空间由*zero 和 *one占用。如此设计,既完美的保留了叶节点和非叶节点的差异性,又保全了二者同样作为节点的统一属性。此外,还节约了内存,一举多得。
typedef struct huffman_node_tag
{
unsigned char isLeaf;
unsigned long count;
struct huffman_node_tag *parent;
union
{
struct
{
struct huffman_node_tag *zero, *one;
};
unsigned char symbol;
};
} huffman_node;
(2)huffman_code
该结构体的功能是用于存放霍夫曼码表。
numbits用于存放编码的码长。
*bits用于存放编码的码字。
typedef struct huffman_code_tag
{
/* The length of this code in bits. */
unsigned long numbits;
/* The bits that make up this code. The first
bit is at position 0 in bits[0]. The second
bit is at position 1 in bits[0]. The eighth
bit is at position 7 in bits[0]. The ninth
bit is at position 0 in bits[1]. */
unsigned char *bits;
} huffman_code;
3.霍夫曼编码器
(1)huffman_encode_file()
以下便是huffman编码的编码器。
其先通过get_symbol_frequencies()对文件进行一次扫描,获得文件中的各个信源符号的概率,完成建立码树的准备工作。
再通过calculate_huffman_codes(),按照霍夫曼编码的原理的四个步骤得出码表。
接下来的过程便是水到渠成的,再对文件进行一次扫描,通过码表得出编码后的文件。
/*
* huffman_encode_file huffman encodes in to out.
*/
int
huffman_encode_file(FILE *in, FILE *out)
{
SymbolFrequencies sf;
SymbolEncoder *se;
//modify by Wu Ruocheng
//date:2017.4.24
SymbolInformation si;
huffman_node *root = NULL;
int rc;
unsigned int symbol_count;
/* Get the frequency of each symbol in the input file. */
symbol_count = get_symbol_frequencies(&sf, in);
//modify by Wu Ruocheng