1. 程式人生 > >C++小專案 — 基於huffman壓縮演算法的檔案壓縮專案

C++小專案 — 基於huffman壓縮演算法的檔案壓縮專案

先去讀配置檔案,構建huffman樹和huffman編碼,用壓縮檔案裡的編碼去huffman樹中查詢,找到對應的葉子結點. 就把葉子結點的字元寫入到解壓縮 檔案中. 所以總結起來也就是那麼幾步: 1.讀取配置檔案,統計所有字元的個數. 2.構建huffman樹,讀解壓縮檔案,將所讀到的編碼字元的這個節點所含的字元,寫到解壓縮的檔案中,直到將壓縮檔案讀完 3.解壓縮完成之後,利用軟體測試,再然後測試一下壓縮率. 好啦話不多說 上程式碼 分析過程: 
//構建huffman編碼的函式.
void GenerateHuffmanCode(Node* root)
{

	if (root == NULL)
	{
		return;
	}
	if (root->_left == NULL &&root->_right == NULL)
	{
		string& code = _infos[(unsigned char)root->_w._ch]._code;
		Node* cur = root;
		Node* parent = cur->_parent;
		while (parent)
		{
			if (parent->_left == cur)
			{
				code.push_back('0');
			}
			if (parent->_right == cur)
			{
				code.push_back('1');
			}
			cur = parent;
			parent = cur->_parent;
		}
		reverse(code.begin(), code.end());
	}
	GenerateHuffmanCode(root->_left);
	GenerateHuffmanCode(root->_right);

}

//解壓縮檔案
void Uncompress(const char* filename)
{

	//為了區分解壓出來的檔案,不覆蓋原檔名.
	assert(filename);

	struct _huffmanInfo
	{
		char _ch;
		LongType _count;
	};
	string uncompressFile = filename;
	//讓pos等於找到第一個'.'出現的的下標位置
	size_t pos = uncompressFile.rfind('.');
	assert(pos != string::npos);
	//從0開始取pos個字元
	uncompressFile = uncompressFile.substr(0, pos);
	uncompressFile += ".unhuffman";
	FILE* fIn = fopen(uncompressFile.c_str(), "wb");
	size_t size;
	assert(fIn);
	FILE* fout = fopen(filename, "rb");

	//往解壓縮檔案中維護的_infos表當中寫資料,然後利用這個_infos表生產huffman樹.
	while (1)
	{
		_huffmanInfo info;
		size = fread(&info, sizeof(_huffmanInfo), 1, fout);
		assert(1 == size);
		if (info._count)
		{
			_infos[(unsigned char)info._ch]._ch = info._ch;
			_infos[(unsigned char)info._ch]._count = info._count;
		}
		else

			break;
	}

	//重新構建huffman樹.
	CharInfo invalid;
	invalid._count = 0;
	HuffmanTree<CharInfo> tree(_infos, 256, invalid);

	//解壓縮
	Node* root = tree.GetRoot();
	GenerateHuffmanCode(tree.GetRoot());
	//統計一共有多少個數據.
	LongType charcount = root->_w._count;

	char value;
	value = fgetc(fout);
	Node* cur = root;
	int count = 0;

	//往解壓縮檔案當中開始寫入資料. 在這裡直接針對性的先統計出所有元素的個數.
	//然後按照個數進行迴圈,寫完迴圈結束.
	while (charcount)
	{
		for (int pos = 7; pos >= 0; --pos)
		{
			if (value & (1 << pos)) //1
				cur = cur->_right;
			else                    //0
				cur = cur->_left;
			if (cur->_left == NULL && cur->_right == NULL)
			{
				fputc(cur->_w._ch, fIn);
				cur = root;
				--charcount;
				if (charcount == 0)
				{
					break;
				}
			}
		}
		value = fgetc(fout);
	}
	fclose(fIn);
	fclose(fout);
}
最後我們終於成功的,大概列出來程式碼的框架和主題思想,接下來就是我在寫程式碼當中碰到的一些BUG和錯誤,我將這些總結起來. (1).剛開始寫的時候測試發現如果待壓縮檔案中出現了中文,程式就會崩潰,將錯誤定位到構建哈夫曼編碼的函式處,最後發現是陣列越界的錯誤 ,因為如果只是字元,它的範圍是-128~127,程式中使用char型別為陣列下標(0~127),所以字元沒有問題. 但是漢字的編碼是兩個位元組,所以可能會 出現越界,解決方法就是將char型別強轉為unsigned char,可表示範圍為0~255. (2)檔案恢復的時候需要注意那些問題?  有些特殊字元在處理需要注意一下,比如'\n',我的程式中有一個函式就是讀取一行字元,但是若是該字元本身就是一個'\n'呢? 這就非常的棘手 了. 對於這個問題一定好好好處理,讀取配置檔案時若讀到了'\n',則說明該字元就是'\n',應該繼續讀取它的次數. (3)解壓縮檔案生成後丟失了大部分的內容 這個BUG困擾我很久,最後為了測試我讓程式打印出在壓縮的時候它寫入了多少次,然後再讓程式在解壓縮的時候列印自己讀出了多少次. 最後我發現 在解壓縮讀出的時候出現了問題,究其原因我又發現了很久,我發現它過早的退出了迴圈,那麼真相只有一個就是程式讀到了EOF,但是我在文字末尾 才會是EOF啊,因為解壓縮讀取是二進位制方式讀寫的,檔案這麼長! 他可能碰巧讀到了EOF對於的二進位制程式碼. 然後就退出啦.  解決方案: 使用feof()函式或者記錄字元資料個數然後根據個數迴圈,這兩種方法都可以解決上述問題. 這個專案其實就是利用我們學習過的堆和huffman樹的實際應用來解決我們實際生活的一些小問題,這種小專案還需要我們多多練習呢~  利用程式碼解決生活的問題才是程式設計的意義,所以呢我們需要將理論和實際相結合~ 這樣我們的學習就會越來越好~