1. 程式人生 > >資料結構————檔案壓縮(利用哈夫曼編碼實現)

資料結構————檔案壓縮(利用哈夫曼編碼實現)

檔案壓縮原理:
首先檔案壓縮是通過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];
};