文件压缩-HuffMan压缩

什么是HuffMan压缩:
简单来说就是利用Huffman树生成Huffman编码,对文件重复出现的字符进行记录,以减少出现次数。从而达到压缩文件的目的。
为什么HuffMan就能实现文件压缩呢?
数据在硬盘中的存储是有格式的,比如说字符就是char类型的,占了8个比特位,但是实际上有些字符可能根本就用不了8个比特位,因此就造成了空间的浪费。而huffman就是根据字符出现的次数重新建立存储规则,减少这些空间浪费。
什么是HuffMan树?

定义: Huffman树,又称最优二叉树,是加权路径长度最短的二叉树。
带权路径长度 = 节点的权值 * 当前节点距离根节点的路径长度
在这里插入图片描述

如何构建HuffMan树?

在这里插入图片描述

生成HuffMan编码

HuffMan树中左子树路径标记为0,右子树路径标记为1.从根节点到叶子结点的编码就是该字符的Huffman编码。
在这里插入图片描述

HuffMan文件压缩原理:

1.统计待压缩文件中每个字符出现的次数

  • 构建结构体,存放信息:字符,次数,编码
  • 遍历源文件,将每个字符的次数写入对应的结构体信息中

2.将字符出现的次数作为权值构建Huffman树

  • 普通的二叉树只能通过双亲节点找到子节点,但是在获取编码的时候需要通过叶子结点往根节点走,所有构建二叉树时,要有子节点指向双亲节点

3.通过Huffman树获取每个字符所对应的编码

  • 通过叶子结点找根节点的编码是逆序的,所以要使用reverse

4.向压缩文件中写入信息

  • 压缩文件中只保存压缩的文件内容是不够的,还要有一些记录源文件内容的信息
  • 第一行:存放源文件的后缀,因为在解压缩文件的时候需要后缀
  • 第二行:存放源文件字符,次数 的总行数
  • 第三行:存放字符和字符出现的次数–为了解压缩重建Huffman树
  • 剩余内容是压缩编码
    例如:源文件名为“1.txt”,存放“ABBBCCCCCDDDDDDD”
    在压缩文件中的模型为:
    在这里插入图片描述
解压缩:

1.获取后缀
2.获取字符以及对应的次数
3.重建Huffman树
4.解压压缩数据

  • 从压缩文件中读取一个字节ch
  • 从根节点开始,按照ch的8个比特位信息(代表字符的编码)从高到低遍历Huffman树:
    a.该比特位是0,取当前节点的左孩子,否则取右孩子
    b.一直遍历,直到遍历到叶子节点位置,该字符就被解析成功,讲解压出来的字符写入文件
    c.如果在遍历Huffman过程中,8个比特位已经比较完毕还没有到达叶子节点,就从第四步开始执行
    d.重复以上过程,直到所有的数据解析完毕
问题:

1.压缩汉字时候程序会崩溃,但是压缩字母程序就能正常运行?

  • 原因:创建存放信息的数据是char 类型的,类型大小是1字节(0~255),但是汉字都是占两个字节的,对应的char类型就会因为超过255而导致数组的下边变成负数,数组下边越界访问导致程序崩溃。
  • 解决方法:将存放信息的数据类型改成unsigned char 类型

2.解压大文件,只能解压缩一部分内容?

  • 原因:一般情况下,文件指针碰到EOF就表示到文件结尾了,因为EOF是 -1,也就是FF,所以只解压了一部分(解压到第一个FF就停止了)。
  • 解决方法:采用 feof()函数,多加一个判断即可,feof()函数就是判断文件末尾,而不仅仅是碰见EOF停止。

3.为什么压缩照片的时候会失败?

  • 原因:是因为 ‘\0’ 的问题,因为如果刚开始把(字节,次数)先写入 buf 中,再由 buf 通过 fwrite 函数写入文件中,一定会出现问题(可能出现 0 字节)。因为 fwrite 的第一个参数要求C格式的字符串,把 buf 转化为C格式的字符串,如果遇见 ‘\0’,就会停止,所以就会崩溃。
  • 解决方法:不要将字节写入 buf 中,而是通过 fputc 直接把字节写入文件中,然后再写入 buf 中,再将buf写入文件即可。

文件压缩的图解过程:

在这里插入图片描述


源码:
main.c

#include "HuffmanTree.hpp"
#include "FileCompressHuffman.h"

int main()
{
   
   
	//TestHuffman();

	FileCompressHuffmanM test;
	//test.CompressFile("文件压缩(原文件).png");

	test.UnCompressFile("文件压缩.hzp");
	return 0;
}



huffmanTree.hpp

#pragma once
#include<iostream>
using namespace std;
#include<vector>
#include<queue>


template<class W>
struct HuffmanTreeNode   //定义哈弗曼树节点的类型
{
   
   
	HuffmanTreeNode(const W& weight)
		:_pLeft(nullptr)
		, _pRight(nullptr)
		, _pParent(nullptr)
		, _weight(weight)
	{
   
   }

	HuffmanTreeNode<W>* _pLeft;
	HuffmanTreeNode<W>* _pRight;
	//用孩子双亲形式表示二叉树,方便从叶子结点找到根节点,从而对字符进行编码
	HuffmanTreeNode<W>* _pParent;

	W _weight;   //节点权值
};

template<class W>
struct Compare //用仿函数比较,变小堆
{
   
   
	typedef HuffmanTreeNode<W>* PNode;
	bool operator()(const PNode pLeft, const PNode pRight)
	{
   
   
		return pLeft->_weight > pRight->_weight;
	}
};


template<class W>
class HuffmanTree
{
   
   
	typedef HuffmanTreeNode<W> Node;
	typedef Node* PNode;
public:
	HuffmanTree()//构造函数,初始状态下为空树
		: _pRoot(nullptr)
	{
   
   }

	void CreatHuffmanTree(const std::vector<W>& v, const W& invalid)  //根据权值创建树,模板引用   vector在标准的命名空间中定义的
	{
   
   
 		if (v.empty())   //v是存放权值的数组
			return;
		//用所给的权值创建二叉树森林
		//std::priority_queue<PNode> q;    使用仿函数实现 小堆  比较器
		std::priority_queue<PNode,std::vector<PNode>,Compare<W>> q;//优先级队列 保存树,把地址放进去就好了,,但是默认是大堆,而我们需要的是小堆

		for (size_t i = 0; i < v.size(); ++i)
		{
   
   
			if (v[i] != invalid)  //过滤出现0次的字符,通过创建哈弗曼树多加上一个参数 来实现
			q.push(new Node(v[i]));  //用权值创建节点
		}
		while (q.size() > 1)  //当树不止一个时,把作为左右孩子
		{
   
   
			PNode pLeft = q.top();
			q.pop();

			PNode pRight = q.top
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值