文件壓縮(小項目)

      Huffman樹,又稱爲最優二叉樹,是加權路徑最短的二叉樹。Huffman樹的構建利用到貪心算法。


【貪心算法】

      貪心算法是指在問題求解時,總是做出當前最好的選擇,也就是說貪心算法做出的不一定是全局的最優解,但是某種意義上的局部最優解。貪心算法不一定能夠求得整體的最優解。


使用貪心算法構建哈夫曼樹:



/*
主要原理:將每個字符與哈夫曼編碼相對應,
壓縮:統計字符出現的次數-》構建哈夫曼樹-》生成哈夫曼編碼-》壓縮到新的文件中
解壓:讀壓縮後的文件-》將哈夫曼編碼與字符相對應-》將字符寫入新的文件中

—次數多的字符路徑短,次數少的字符路徑長
*/


--heap.h

#pragma once
//堆
#include <vector>
#include <assert.h>

//添加仿函數
template<class T>
struct Less
{
	bool operator()(const T* L, const T* R)
	{
		return L < R;
	}
};

template<class T>
struct Greate
{
	bool operator()(const T* L, const T* R)
	{
		return L > R;
	}
};

template <class T, class compare = Less>     //Less爲默認的類型
//template <class T, template<class> compare = Less>     //模板的模板參數
class Heap
{
public:
	Heap()     //無參構造
		:_size(0)
	{}

	Heap(vector<T>& a)     //拷貝構造
	{
		_a.swap(a);         //vector中swap函數?
		//建堆
		for (int i = (_a.size() - 2) / 2; i >= 0; --i)
		{
			AdjustDown(i);
		}
	}

	Heap(const T* a, size_t size)
	{
		_a.reverse(size);   //申請空間
		for (int i = 0; i < size; i++)    //將所有元素壓入容器中
		{
			_a.push_back(a[i]);
		}
		for (int i = (_a.size() - 2) / 2; i >= 0; i--)   //i爲元素下標,調整堆
		{
			_AdjustDown(i);
		}
	}

	void Push(const T& x)      //將數據插入堆中
	{
		_a.push_back(x);
		AdjustUp(_a.size() - 1);    //進行向上調整
		++_size;
	}

	void Pop()       //刪除數據
	{
		size_t size = _a.size();
		assert(size > 0);
		//將堆的頭元素和尾元素進行交換,然後進行pop,對堆進行調整
		swap(_a[0], _a[size - 1]);
		_a.pop_back();
		AdjustDown(0);
		--_size;
	}

	T Top()   //堆頂上元素
	{
		return _a[0];
	}

	size_t Size()     //求堆中數據的個數
	{
		return _size;
	}

public:
	void AdjustDown(size_t parent)     //將堆進行下調(小堆)
	{
		size_t child = parent * 2 + 1;   //計算孩子節點的下標
		size_t size = _a.size();
		while (child < size)
		{
			compare com;
			if (child + 1 < size && com(_a[child + 1], _a[child]))      //尋找左右孩子中最小的
			{
				++child;
			}
			if (com(_a[child], _a[parent]))    //將最大的孩子與父節點進行比較
			{
				swap(_a[parent], _a[child]);
				parent = child;
				child = parent * 2 + 1;
			}
			else
			{
				break;
			}
		}
	}
	void AdjustUp(size_t child)   //上調
	{
		size_t parent = (child - 1) / 2;
		while (child > 0)
		{
			compare com;
			if (com(_a[child], _a[parent]))
			{
				swap(_a[child], _a[parent]);
				child = parent;
				parent = (child - 1) / 2;
			}
			else
			{
				break;
			}
		}
	}
protected:
	vector<T> _a;
	size_t _size;
};


--Huffman.h

#pragma once
#include "Heap.h"
//實現哈夫曼樹

template <class T>
struct HuffmanNode
{
	HuffmanNode<T>* _left;    //指向哈夫曼樹的左節點的指針
	HuffmanNode<T>* _right;   //指向哈夫曼樹的右節點的指針
	T _weight;     //節點的權值

	HuffmanNode(const T& x)
		:_left(NULL)
		, _right(NULL)
		, _weight(x)
	{}
};

template <class T>
class HuffmanTree
{
	typedef HuffmanNode<T> Node;
public:
	HuffmanTree()       //進行無參構造
		:_root(NULL)
	{}

	~HuffmanTree()      //析構函數
	{
		_clear(_root);
	}

	//構造哈夫曼樹
	HuffmanTree(T* a, size_t size, const T& invalid)
	{
		_root = CreateTree(a, size, invalid);
	}

	Node* GetRootNode()     //獲得根節點
	{
		return _root;
	}

protected:
	Node* CreateTree(T* a, size_t size, const T& invalid)
	{
		struct compare     //構造仿函數
		{
			bool operator()(const Node* dt, const Node* st)
			{
				return dt->_weight < st->_weight;
			}
		};

		Heap<Node*, compare> minHeap;     //創建最小堆

		for (size_t i = 0; i < size; ++i)//將所有的數據壓入堆中
		{
			if (a[i] != invalid)
			{
				minHeap.Push(new Node(a[i]));
			}
		}
		Node* parent = new Node(0);
		while (minHeap.Size() > 1)      //小堆的大小不爲空
		{
			Node* left = minHeap.Top();     //將小堆中最小的兩個數據取出
			minHeap.Pop();
			Node* right = minHeap.Top();
			minHeap.Pop();

			parent = new Node(left->_weight + right->_weight);   
			parent->_left = left;
			parent->_right = right;
			minHeap.Push(parent);
		}
		return parent;
	}

	void _clear(Node* root)
	{
		if (root)
		{
			_clear(root->_left);
			_clear(root->_right);
			delete root;
		}
	}

protected:
	Node* _root;
};

--FileCompress.h

#pragma once

//利用哈夫曼樹實現文件壓縮
/*
主要原理:將每個字符與哈夫曼編碼相對應,
壓縮:統計字符出現的次數-》構建哈夫曼樹-》生成哈夫曼編碼-》壓縮到新的文件中
解壓:讀壓縮後的文件-》將哈夫曼編碼與字符相對應-》將字符寫入新的文件中

次數多的字符路徑短,次數少的字符路徑長
*/
#include "Huffman.h"
#include <string>
#include <math.h>

typedef long long LongType;
struct charInfo
{
	unsigned char _ch;    //字符
	LongType _count;        //出現的次數
	string _code;           //哈夫曼編碼

	charInfo()
		:_count(0)
		, _ch(0)
	{}

	charInfo(const LongType& count)        //構造
		:_count(count)
		, _ch(0)
	{ }

	bool operator!=(const charInfo& info)const    //重載!=(常成員函數)
	{
		return _count != info._count;
	}

	charInfo operator+(const charInfo& info)const       //重載+
	{
		return charInfo(_count + info._count);
	}

	bool operator<(const charInfo& info)const      //重載<
	{
		return _count < info._count;
	}
};
 

class FileCompress
{
public:
	FileCompress()     //無參構造
	{
		for (int i = 0; i < 256; ++i)
		{
			_infos[i]._count = 0;
			_infos[i]._ch = i;
		}
	}

	void compress(const char* Filename)    //壓縮
	{
		assert(Filename);
		FILE* FOut = fopen(Filename, "rb");     //打開源文件
		assert(FOut);      //判斷打開文件是否失敗

		//使用直接定址法統計每個字符出現的次數
		char ch = fgetc(FOut);    //取一個字符
		while (!feof(FOut))
		{
			_infos[(unsigned char)ch]._count++;
			ch = fgetc(FOut);
		}

		//構建哈夫曼樹
		charInfo invalid(0);
		HuffmanTree<charInfo> tree(_infos, 256, invalid);    

		//生成哈夫曼編碼
		string code;
		GenerateHuffmanCode(tree.GetRootNode(),*this, code);
		/*HuffmanNode<charInfo>* root = tree.GetRootNode();
		GenerateHuffmanCode(root, *this, code);*/


		//寫配置文件,方便解壓縮時重建HuffmanTree。
		/*新建配置文件,給中間放入讀取的字符種類,不需要存每個字符讀取的次數,
		可以直接利用哈夫曼樹的根節點的權值,就可以確定文件字符的總個數*/
		string configFile = Filename;
		string compressFileName = Filename;   //新建壓縮文件
		size_t last_ = configFile.find_last_of('.');
		if (last_ < configFile.size())
		{
			configFile.erase(last_);
			compressFileName.erase(last_);
		}

		configFile += ".config";
		FILE* FInconfig = fopen(configFile.c_str(), "wb");
		assert(FInconfig);

		string str;     //使用str來保存出現的字符
		char buffer[20] = {0};
		for (size_t i = 0; i < 256; ++i)
		{
			if (_infos[i]._count != 0)
			{
				str += _infos[i]._ch;
				str += ':';
				str += (string)_itoa(_infos[i]._count, buffer, 10);
				//str += buffer;
				str += '\n';
				fputs(str.c_str(), FInconfig);
				str.clear();      //每次對str進行清除,就能夠保存下一個出現的字符
			}
			
		}


		//將文件進行壓縮
		
		compressFileName += ".compress";
		FILE* FIn = fopen(compressFileName.c_str(), "wb");
		assert(FIn);

		fseek(FOut, 0, SEEK_SET);       //將fout文件指針移動到0的位置
		ch = fgetc(FOut);
		unsigned char value = 0;
		int pos = 0;
		while (!feof(FOut))    //將每個字符的編碼寫入文件
		{
			str = _infos[(unsigned char)ch]._code;
			for (size_t i = 0; i < str.size(); ++i)  
			{
				value <<= 1;
				value |= (str[i] - '0');
				if (++pos == 8)
				{
					fputc(value, FIn);
					pos = 0;
					value = 0;
				}
			}
			ch = fgetc(FOut);
		}
		//如果編碼最後一個寫入時,一個字符的空間沒有佔滿時,採用的方式是進行補0操作
		if (pos > 0)     
		{
			value <<= (8 - pos);
			fputc(value, FIn);
		}
		
		fclose(FIn);
		fclose(FOut);
		fclose(FInconfig);
	}


	void unCompress(const char* Filename)    //解壓
	{
		assert(Filename);
		FILE* FOut = fopen(Filename, "rb");
		assert(FOut);

		//配置文件
		string configFile = (string)Filename;
		string FileInName = (string)Filename;

		size_t last_ = configFile.find_last_of('.');   //查找字符串中出現的最後一個‘.’
		if (last_ < configFile.size())
		{
			configFile.erase(last_);    //將後面的字符進行刪除
			FileInName.erase(last_);
		}
		configFile += ".config";
		FILE* FConfig = fopen(configFile.c_str(), "rb");
		assert(FConfig);

		//解壓後的文件
		FileInName += "_com.txt";
		FILE* FIn = fopen(FileInName.c_str(), "wb");
		assert(FIn);

		//修改_count,注意\n,有可能代表字符,有可能是行結束標誌
		char buff[20] = { 0 };
		unsigned char ch = fgetc(FConfig);
		while (!feof(FConfig))
		{
			fgetc(FConfig);
			fgets(buff, 20, FConfig);
			this->_infos[ch]._count = (LongType)atoi(buff);
			ch = fgetc(FConfig);
		}
		
		//重建哈夫曼樹
		charInfo invalid(0);    //定義非法值
		HuffmanTree<charInfo> tree(_infos, 256, invalid);
		HuffmanNode<charInfo>* root = tree.GetRootNode();  
		HuffmanNode<charInfo>* cur = root;
		ch = fgetc(FOut);
		int count = root->_weight._count;    //記錄字符的總個數控制結束,根節點的權值表示字符的總個數
		int pos = 7;
		while (count > 0)     //讀取文件的編碼
		{
			while (pos >= 0)
			{
				if (ch & (1 << pos))
				{
					cur = cur->_right;
				}
				else
				{
					cur = cur->_left;
				}
				if (cur->_left == NULL && cur->_right == NULL)
				{
					fputc(cur->_weight._ch, FIn);
					if (--count == 0)       //將剩餘沒有寫入的字符總次數減1 
					{
						break;
					}
					cur = root;
				}
				--pos;
			}
			pos = 7;
			ch = fgetc(FOut);
		}
		fclose(FOut);
		fclose(FIn);
		fclose(FConfig);
	}

	/*void PrintCode()const
    {
		for (int i = 0; i < 256; ++i)
		{
			if (this->_infos[i]._count != 0)
			{
				cout << this->_infos[i]._ch << ":>" << this->_infos[i]._code << endl;
			}
		}
	}*/

protected:
	//後序遍歷生成哈夫曼編碼(使用遞歸)
	void GenerateHuffmanCode(HuffmanNode<charInfo>* root, FileCompress& file, string code)
	{
		if (root == NULL)
		{
			return;
		}
		if (root->_left == NULL && root->_right == NULL)    //葉子節點
		{
			file._infos[root->_weight._ch]._code = code;
			return;
		}
		GenerateHuffmanCode(root->_left, file, code + '0');    //string類型的可以直接進行+追加字符
		GenerateHuffmanCode(root->_right, file, code + '1');    //左加0,右加1	
	}


protected:
	charInfo _infos[256];
};

方法:
//記錄補字符的位數,
//解壓和壓縮的字符是相同的,
//源文件出現字符的次數爲哈夫曼樹的根節點的值,每處理一個字符,減值進行減減

--testRunningTime.h

#pragma once 
#ifndef __TIME_CHECK_H__
#define __TIME_CHECK_H__

#include <windows.h>

class MyTimer
{
	public:
		MyTimer()
		{
			QueryPerformanceFrequency(&_freq);
		 	costTime = 0.0;
		}

		void Start()
		{
			for (int i = 0; i<EN_NUMER; ++i)
				{
					QueryPerformanceCounter(&_array[i]._begin);
				}
		}
		void Stop()
		{
			for (int i = 0; i<EN_NUMER; ++i)
			{
				QueryPerformanceCounter(&_array[i]._end);
			}
		}

		void Reset()
		{
			costTime = 0.0;
		}

		void showTime()
			{
			    double allTime = 0.0;
			    for (int i = 0; i<EN_NUMER; ++i)
				{
					allTime += (((double)_array[i]._end.QuadPart - (double)_array[i]._begin.QuadPart) / (double)_freq.QuadPart);
				}
			    costTime = allTime / EN_NUMER;
			    costTime *= 1000000;

				if ((((int)costTime) / 1000000) > 0)
				{
					cout << costTime / 1000000 << " s" << endl;
				}
			    else if (((int)costTime) / 1000 > 0)
				{
					cout << costTime / 1000 << " ms" << endl;
				}
			    else
				{
					cout << costTime << " us" << endl;
				}
			}

		private:
			class Array
			{
				public:
					LARGE_INTEGER _begin;
					LARGE_INTEGER _end;
					};
			enum{ EN_NUMER = 5 };
			LARGE_INTEGER _freq;
			double costTime;
			Array _array[EN_NUMER];
			};
#endif

--test.cpp

#define _CRT_SECURE_NO_WARNINGS 1

#include <iostream>
using namespace std;

#include "FileCompress.h"
#include "testRunningTime.h"
#include "testRunningTime.h"

void Test()
{
	string filename = "1.txt";
	FileCompress ht;
	ht.compress(filename.c_str());
	//ht.PrintCode();

	string filename1 = "1.compress";
	ht.unCompress(filename1.c_str());
}

void Test1()    //測試壓縮時間
{

	//string filename = "Input.BIG";
	cout << "壓縮時間";
	MyTimer timer;
	timer.Start();

	FileCompress ht;
	ht.compress("Input.BIG");

	timer.Stop();
	timer.showTime();

}

void Test2()     //測試解壓時間
{
	//string filename = "compressFileName";
	cout << "解壓時間";
	MyTimer timer;
	timer.Start();

	FileCompress ht;
	ht.unCompress("Input.compress");

	timer.Stop();
	timer.showTime();
}


int main()
{
	//Test();
	Test1();
	Test2();
	system("pause");
	return 0;
}




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章