数据结构————文件压缩(利用哈夫曼编码实现)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/FangXiaXin/article/details/83096899

文件压缩原理:
首先文件压缩是通过HuffmaCode实现的、整体思路通过读取文件获取字符出现频率,通过字符出现频率可以构建HuffmanTree,每个文件中出现的字符通过HuffmanTree获取HuffmanCode,从而将文件中的字符同过HuffmanTree获取相应编码,并写入压缩文件,从而完成文件压缩。
为什么通过HuffmanCode可以实现文件的压缩呢?
原因:1.每个文件的字符种类有限。2.每个字符出现次数可能很多。3.HuffmanTree构造的特点:HuffmanTree越靠近根节点的叶子节点权值越大,路径越短;如果文件中的字符重复的越多,靠近根节点的叶子节点的权值越大,而这些字符的路径最短所以HuffmanCode就越短,这样在这些字符多的情况下压缩率越高。
如何产生HuffmanCode?
1.产生HuffmanCode的条件是先构建HuffmanTree,从构建HuffmanTree的节点中选出权值最小、权值较小的节点;从而形成左右子树,再左右子树的权值相加,作为父亲节点的权值;再将父亲节点入到构建huffmanTree的节点集合中;再继续选最小、次小,直到集合中剩下一个节点时候,这个节点为根节点。
2.通过遍历HuffmanTree的所有叶子节点的路径来获取每个文件中出现的字符的HuffmanCode。
了解了这些压缩原理后,我们该如何实现文件压缩呢?
文件压缩流程:
1.读取文件,统计字符出现次数。
2.通过统计出来的字符出现次数,来构建HuffmanTree。
3.获取HuffmanCode。
4.写如配置信息(每个字符出现的次数,方便解压时重构huffmanTree)。
5.读取文件,将文件中出现的字符的HuffmanCode依次压缩进压缩文件。
文件解压流程:
1.读取配置信息。
2.通过读取信息重构HuffmanTree。
3.通过编码遍历HuffmanTree来获取字符写入压缩文件。
关于构建HuffmanTree用到技术
1.堆:通过小堆来选取最小值和次小值。(前面有一篇讲堆的博客)。
2.仿函数:在堆中用到了仿函数(通过仿函数来比较CharInfo中的权重大小来建堆)。
3.HuffmanCode:因为HuffmanTree所有的信息的都在叶子节点中,而且每个叶子节点都有唯一确定的路径(根节点到叶子节点的路径),即这条路径就为HuffmanCode,而每个叶子节点又对应唯一的字符。
4.string类:用来保存HuffmanCode。
文件压缩的部分细节:
统计文件中所有字符的出现次数。由于Ascall码字符一共255个,只有前128个字符可以显示,定义字符变量时一定要定义成无符号型变量unsigned char ch如下,这是ch读不到文件的结束标志,所以我们可以用函数feof来代替文件的结束标志EOF,最重要的是文件的打开方式一定要是二进制的形式打开否则读不到汉字字符,将出现乱码。而这255个字符我们将采取哈希映射的方式来存储在_info[256]数组中。以数组下标映射每个字符,以方便字符与出现的次数相对应起来。在数组元素中保存字符编码和字符对应的次数。方便我们引索。
文件压缩各部分头文件
Heap.h:

#pragma once
#include<iostream>
#include<vector>
#include<stdio.h>
#include<assert.h>
using namespace std;

template<class T>
struct Less
{
	bool operator()(const T& l, const T& r)
	{
		return l < r;
	}
};
template<class T>
struct Great
{
	bool operator()(const T& l, const T& r)
	{
		return l>r;
	}
};

template<class T, class compare = Less<T>>
class Heap
{
public:
	Heap()
	{}
	Heap(T* a, size_t size)
	{
		_array.reserve(size);
		for (size_t i = 0; i < size; i++)
		{
			_array.push_back(a[i]);
		}
		//建堆,从最后一个叶子节点,调用向下调整
		for (int i = (size - 2) / 2; i >= 0; --i)
		{
			AdjustDown(i);
		}
	}
	void Push(const T&data)
	{
		_array.push_back(data);
		AdjustUp(_array.size() - 1);
	}
	void Pop()
	{
		if (!_array.empty())
		{
			swap(_array[0], _array[_array.size() - 1]);
			_array.pop_back();
			AdjustDown(0);
		}
	}
	const T& Top()
	{
		assert(!_array.empty());
		return _array[0];
	}
	bool Empty()
	{
		return _array.empty();
	}
	size_t Size()
	{
		return _array.size();
	}
private:
	void AdjustUp(int root)
	{
		int child = root;
		int parent = (child - 1) / 2;
		compare com;
		while (parent >= 0)
		{
			if (com(_array[child], _array[parent]))
			{
				swap(_array[child], _array[parent]);
				child = parent;
				parent = (child - 1) / 2;
			}
			else
				break;
		}
	}
	void AdjustDown(int root)
	{
		size_t parent = root;
		size_t child = root * 2 + 1;
		compare com;
		while (child < _array.size())
		{
			if (child + 1 < _array.size() && com(_array[child + 1], _array[child]))
				child++;
			if (com(_array[child], _array[parent]))
			{
				swap(_array[child], _array[parent]);
				parent = child;
				child = parent * 2 + 1;
			}
			else
			{
				break;
			}
		}
	}
	vector<T> _array;
};

HuffmanTree.h:

#pragma once
#include"Heap.h"
template<class W>
struct HuffmanNode
{
	HuffmanNode* _left;
	HuffmanNode* _right;
	HuffmanNode* _parent;
	W _w;//权重
	HuffmanNode(const W& w)
		:_left(NULL)
		, _right(NULL)
		, _parent(NULL)
		, _w(w)
	{}
};

template<class W>
class HuffmanTree
{
public:
	typedef HuffmanNode<W> Node;//HuffmanNode 命名为Node
	HuffmanTree()
	{
		root = NULL;
	}
	HuffmanTree(W* w, size_t size, const W& invalid)//创建huffmantree的数组w,数组大小 size,无效元素
	{
		//1.建堆来,以赛选最小值、次小值构造Huffmantree
		//用来作比较的仿函数
		struct NodeCompare
		{
			bool operator()(Node* l,  Node* r)
			{
				return l->_w < r->_w;
			}
		};
		//2.建堆
		Heap<Node*, NodeCompare> minheap;
		for (size_t i = 0; i < size; i++)
		{
			if (w[i] != invalid)
			{
				minheap.Push(new Node(w[i]));
			}
		}
		//3.构建Huffmantree
		while (minheap.Size()>1)
		{
			Node* left = minheap.Top();//最小值
			minheap.Pop();
			Node* right = minheap.Top();//次小值
			minheap.Pop();
			Node* parent = new Node(left->_w + right->_w);
			parent->_left = left;
			parent->_right = right;
			left->_parent = parent;
			right->_parent = parent;
			minheap.Push(parent);
		}
		root = minheap.Top();
	}
	Node* GetRoot()
	{
		return root;
	}
private:
	//将拷贝构造和复制运算符重载封装为私有
	HuffmanTree(const HuffmanTree<W>& tree);
	HuffmanTree<W>& operator=(const HuffmanTree<W>& tree);
	Node* root;
};

FileCompress.h

#pragma once

#include<iostream>
using namespace std;

#include<assert.h>
#include<string>

#include"Huffman.h"

//存储字符信息,以方便构造HuffmanTree
struct CharInfo
{
	char _ch;
	string _code;//存储Huffman编码
	long long _count;//ch出现的次数
	bool operator !=(const CharInfo& info)
	{
		return _count!= info._count;
	}
	bool operator <(const CharInfo& info)
	{
		return _count < info._count;
	}
	CharInfo operator +(const CharInfo& info)
	{
		CharInfo ret;
		ret._count = _count+info._count;
		return ret;
	}
};

class FileCompress
{
	typedef HuffmanNode<CharInfo> Node;
	//配置信息
	struct ConfInfo
	{
		char _ch;
		long long _count;
	};
public:
	//在构造函数中初始化_info[]
	FileCompress()
	{
		for (int i = 0; i < 256; i++)
		{
			_info[i]._ch = i;
			_info[i]._count = 0;
		}
	}
	//文件压缩
	void Compress(char* file)
	{
		//1.读取文件,统计字符个数
		FILE* fout = fopen(file, "rb");
		assert(fout);
		char ch = getc(fout);
		while (!feof(fout))
		{
			_info[(unsigned char)ch]._count++;
			ch = getc(fout);
		}
		//2.构建HuffmanTree
		CharInfo invalid;
		invalid._count = 0;
		HuffmanTree<CharInfo> tree(_info, 256, invalid);

		//3.生成Huffman编码
		string code;
		Node* root = tree.GetRoot();
		GetHuffmanCode(root, code);
		//4.读取文件,写入配置信息;以方便解压缩时构建HuffmanTree
		string Comfile = file;
		Comfile += ".Compress";
		FILE* fin = fopen(Comfile.c_str(), "wb");
		ConfInfo info;
		for (int i = 0; i < 256; i++)
		{
			if (_info[i]._count != 0)
			{
				info._ch = _info[i]._ch;
				info._count = _info[i]._count;
				fwrite(&info, sizeof(ConfInfo), 1, fin);
			}
		}
		
		//写入最后一个为配置文件标记位
		info._count = 0;
		fwrite(&info, sizeof(ConfInfo), 1, fin);
		//5.压缩
		fseek(fout, 0, SEEK_SET);
		char value = 0;
		int pos = 0;
		Node* cur = root;
		ch = fgetc(fout);
		while (!feof(fout))
		{
			string code = _info[(unsigned char)ch]._code;
			for (int i = 0; i < code.size(); i++)
			{
				if (code[i] == '1')
					value |= (1 << pos);
				else if (code[i] == '0')
					value &= ~(1 << pos);
				else
					assert(false);
				pos++;
				if (pos == 8)
				{
					putc(value, fin);
					pos = 0;
					value = 0;
				}
			}
			ch = getc(fout);
		}
		if (pos != 0)
			putc(value, fin);
		//关闭打开的文件
		fclose(fout);
		fclose(fin);
	}
	void UnCompress(char* file)
	{
		//1.读取配置文件
		assert(file);
		FILE* fout = fopen(file, "rb");
		assert(fout);
		while (1)
		{
			ConfInfo info;
			fread(&info, sizeof(ConfInfo), 1, fout);
			if (info._count == 0)
				break;
			_info[(unsigned char)info._ch]._count = info._count;
		}
		//2.重建huffmanTree
		CharInfo invalid;
		invalid._count = 0;
		HuffmanTree<CharInfo> tree(_info,256,invalid);
		//3.解压缩
		string UnCompress = file;
		size_t find = UnCompress.rfind('.');
		UnCompress.erase(find, UnCompress.size() - find);
		UnCompress += ".UnCompress";
		FILE* fin = fopen(UnCompress.c_str(), "wb");
		Node* root = tree.GetRoot();
		Node* cur = root;
		long long count = root->_w._count;
		char value = fgetc(fout);
		while (!feof(fout))
		{
			for (size_t pos = 0; pos < 8; pos++)
			{
				if (value & (1 << pos))
					cur = cur->_left;
				else
					cur = cur->_right;
				if (cur->_left == NULL && cur->_right == NULL)
				{
					putc(cur->_w._ch, fin);
					cur = root;
					count--;
					if (count == 0)
						break;
				}
			}
			value = getc(fout);
		}
		fclose(fout);
		fclose(fin);
	}
private:
	void GetHuffmanCode(Node* root, string code)//获取HuffmanCode
	{
		if (root == NULL)
			return;
		if (root->_left == NULL && root->_right == NULL)
		{
			_info[(unsigned char)root->_w._ch]._code = code;
			return;
		}
		GetHuffmanCode(root->_left, code + '1');
		GetHuffmanCode(root->_right, code + '0');
	}
	CharInfo _info[256];
};

猜你喜欢

转载自blog.csdn.net/FangXiaXin/article/details/83096899
今日推荐