基于哈夫曼树的文件压缩

准备工作
//将字符信息包装成为一个结构体
struct CharInfo{
	CharInfo(size_t _charCount = 0)
	:_charCount(_charCount)
	{}
	CharInfo operator+(const CharInfo& info)
	{
		return CharInfo(_charCount + info._charCount);
	}
	bool operator>(const CharInfo& info)
	{
		return (_charCount > info._charCount);
	}
	bool operator!=(const CharInfo& info)const
	{
		return (_charCount != info._charCount);
	}
	bool operator==(const CharInfo& info) const
	{
		return (_charCount == info._charCount);
	}
	unsigned char _ch;				//字符
	long long _charCount;			//字符出现的次数
	string _strCode;				//字符的对应编码
};
//将哈弗曼树的节点包装为一个结构体
template<class W>

struct HuffmanTreeNode{
	HuffmanTreeNode(const W& weight)
		: _parenet(nullptr)
		,_lchild(nullptr)
		,_rchild(nullptr)
		,_weight(weight)
	{}
	HuffmanTreeNode* _parenet;
	HuffmanTreeNode* _lchild;
	HuffmanTreeNode* _rchild;
	W _weight;
};

一、压缩

1.获取原文件中每个字符出现的次数

首先我们需要统计一下文档中每个字符出现的次数,由于是测试阶段,我们可以将文档的内容编辑的较为简单,便于我们进行调试观察,在这里我用来测试的文档内容为:
“ABBBCCCCCDDDDD”
因为字符的编码有限,所以我采取的哈希表的方式来进行统计,大小为256个元素的数组,当每次读到一个字符时,将该字符的ASCII码作为下标,对应元素值(数组中的每一个元素均为一个 CharInfo 的结构体)的次数进行加一,至于为什么要将字符类型设置为无符号类型,因为其中涉及到字符编码的问题。。。这个我们就不深究了,总之用到无符号类型的时候,即使是汉字也会被压缩进去,不然的话,程序在压缩文本文档时就会崩溃。
在这里插入图片描述

2.以每个字符出现的次数作为权值构建哈夫曼树

在我们得到每个字符的出现次数之后,我们可以以该次数为权值创建哈夫曼树,所以每个叶子结点上的权值即为字符在文章中出现的次数,规定向左子树方向为0,右子树方向为1,这样一来就为后续第三步获取字符编码作基础。
在这里插入图片描述

3.根据哈弗曼树获取每个字符的编码

因为第二步已经将哈弗曼树建好,所以这一步的作用就是将每个叶子节点的编码放入到对应叶子结点的编码值中去,为了方便起见,我将每一个树的节点包装成一个结构体,通过它的叶子结点向上方查找,知道找到根节点为止,不过需要注意的是,由于子节点向上查找到过程中,我们所得到的编码是完全相反的,所以需要调用一下string类中的reverse方法将字符串逆置,然后存储在对应 charInfo数组中。

4.根据每个字符的编码重新改写原文件

在将所有信息记录完成之后,我们就要开始压缩改写原文件了,首先我们需要注意的就是,要先将原文件的后缀名读取出来,存在改写后的文件的第一行,还有要解决的问题就是,为了能够解压缩,我们必须将哈夫曼树保存下来便于解压时查询编码,从而得到对应的字符,但是保存一颗哈弗曼树显然是不可能的,所以我将哈弗曼树的叶子结点信息保存下来,等到解压的时候,可以通过这些信息重新开始建树,从而通过编码得到对应字符,但是问题又来了,我们如何确定当先改写文件中的信息是节点信息而不是对应的编码信息呢?在这里,我是这样解决的,在写完文件后缀之后,再换行输入一个数字n,这个数字n代表的意义就是从当前行开始,往下n行就是对应的叶子结点的信息。这样我们就可以分辨叶子结点信息与编码信息了。
在输入完叶子结点信息之后,我们就要将对应的编码信息进行写入,当原文件字符所匹配到的编码达到8位时,也就是一个字节,就可以进行写入了,那么当文件的结尾不够八位时,我们可以加一个计数器,然后将最后一个编码左移,补全一个字节再进行写入。

二、解压缩

首先,解压缩较为容易,它其实是压缩的一个逆过程,但是里面不乏有很多
”,在最后我会分享一些我在编码时遇到的一些“大坑”,希望读者以后不要再犯类似的错误。
在这里插入图片描述

1. 从压缩文件中获取源文件的后缀

首先将文件指针移到压缩文件开始位置,进行读取一行的数据,将其存在一个字符串中,这就是解压后的文件后缀名

2. 从压缩文件中获取字符次数的总行数 、获取每个字符出现的次数

读取第二行的数据 n,n 就是从这行开始往下 n行,都为压缩的字符信息。
注意!!!
在这里需要强调的是,在压缩的过程中,如果遇到了多个空行,他不会将该换行作为一个字符存储到文件中,就会导致压缩文件中出现空行的问题,在这个地方需要特别注意!当文件指针读取到空行的时候,需要自己手动添加一个换行符,再往下一行读取换行符的个数。

4. 重建huffman树, 解压压缩数据

根据保存下来的权值构建新的huffman树,进而根据压缩文件最后一部分的编码信息,从根节点向下找寻需要写入解压缩文件中的字符。这一步需要特别留心的就是,在文件指针指向最后一个字节信息时,有可能因为压缩过程中编码信息由于不足一个字节而向高位偏移,导致书写出来的内容与原文不匹配,这时候就需要用到我们的文件总大小,也就是重构树的根节点的权值,计算出压缩时最后一个字节的偏移量,从而得到正确的编码信息。

以下附上源代码:
https://github.com/colourlife/1998/tree/master/FileCompress

综合实验: 1. 问题描述 利用哈夫曼编码进行通信可以大大提高信道利用率,缩短信息传输时间,降低传输成本。这要求在发送端通过一个编码系统对待传输数据预先编码,在接收端将传来的数据进行译码(复原)。对于双工信道(即可以双向传输信息的信道),每端都需要一个完整的编/译码系统。试为这样的信息收发站编写一个哈夫曼码的编/译码系统。 2. 基本要求 一个完整的系统应具有以下功能: (1) I:初始化(Initialization)。从终端读入字符集大小n,以及n个字符和n个权值,建立哈夫曼树,并将它存于文件hfmTree中。 (2) E:编码(Encoding)。利用已建好的哈夫曼树(如不在内存,则从文件hfmTree中读入),对文件ToBeTran中的正文进行编码,然后将结果存入文件CodeFile中。 (3) D:译码(Decoding)。利用已建好的哈夫曼树文件CodeFile中的代码进行译码,结果存入文件Textfile中。 (4) P:印代码文件(Print)。将文件CodeFile以紧凑格式显示在终端上,每行50个代码。同时将此字符形式的编码文件写入文件CodePrin中。 (5) T:印哈夫曼树(Tree printing)。将已在内存中的哈夫曼树以直观的方式(比如树)显示在终端上,同时将此字符形式的哈夫曼树写入文件TreePrint 中。 3. 测试数据 用下表给出的字符集和频度的实际统计数据建立哈夫曼树,并实现以下报文的编码和译码:“THIS PROGRAME IS MY FAVORITE”。 字符 A B C D E F G H I J K L M 频度 186 64 13 22 32 103 21 15 47 57 1 5 32 20 字符 N O P Q R S T U V W X Y Z 频度 57 63 15 1 48 51 80 23 8 18 1 16 1
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值