1. 程式人生 > >哈夫曼編碼 (Huffman code)的實現,壓縮、解壓縮

哈夫曼編碼 (Huffman code)的實現,壓縮、解壓縮

此程式首先掃描一遍輸入檔案並統計各個字元的出現次數,然後對結果排序,再由此構造Huffman樹,然後對樹進行一個遍歷,並把各個字元的Huffman編碼存到一個hash表中,所謂hash表就是建立一個string陣列,陣列下標用字元的ASCII碼錶示,陣列內容用此字元對應的Huffman編碼表示,例如,a:11,則 hash['a'] = "11";

然後重新對檔案進行一遍掃描,根據hash表進行壓縮,解壓根據Huffman樹來進行。存到檔案是按8位存的,這個地方費了不少力氣。

要驗證效果,可以在程式目錄下建立“ huffman.in" 文字檔案,進行程式後生成的 huffman.bin是壓縮後的二進位制檔案,可以用UE看其內容,生成的huffman.out為解壓後的檔案,內容和原檔案一樣。

對中文也可壓縮。

/* Huffman Code */
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <iostream>
#include <bitset>
#include <fstream>
#include <time.h>

#define CharCount 256

using namespace std;

/*Huffman Tree's Node*/
struct TreeNode{
	int value;
	int alpha;
	string code;
	TreeNode * lChild;
	TreeNode * rChild;
	TreeNode(){	value = 0;	alpha = 0;	lChild = rChild = NULL;	code="";} //建構函式
};

/*連結串列結點,輔助構造Huffman樹*/
struct ListNode{
	TreeNode huffNode;
	ListNode * child;
	ListNode(){	child = NULL;} //建構函式		
};

//儲存輸入檔案的統計資訊,hash表
struct hashTable{
	int value;
	int alpha;
	hashTable(){alpha=0; value=0;}
}Ascii[CharCount];


//qsort的排序函式
int Comp(const void *a, const void *b)
{
	return *(int *)a - *(int *)b;
}

TreeNode * CreateHuffmanTree(hashTable ascii[])
{
	/*初始化建立二叉樹森林,每個樹只有一個結點,並把這些森林串到一個連結串列中*/
	ListNode * root = new ListNode;
	ListNode * next = root; 	//root指向第一個節點,此節點有資訊
	
	for(int i=0; /*i<127*/; i++)
	{
		if(ascii[i].value == 0)
			continue;
			
		next->huffNode.value = ascii[i].value;
		next->huffNode.alpha = ascii[i].alpha;

		if(i == CharCount-1) //防止多建一個無用的節點
			break;
		
		next->child = new ListNode;
		next = next->child;
	}
	
	//如果森林中的樹>1,就繼續處理,直到森林(連結串列)中只有一顆樹,這時Huffman樹也已建成
	while(root->child != NULL)
	{
		ListNode * p = new ListNode;
		/*把新結點的權值設為最小兩個結點的權值之和*/
		p->huffNode.value = root->huffNode.value + root->child->huffNode.value;

		/*把新結點中的Huffman節點中的左右子樹設定為兩個較小節點中的Huffman節點*/
		p->huffNode.lChild =  &(root->huffNode);
		p->huffNode.rChild = &(root->child->huffNode);
		
		/*從連結串列中刪除最小的兩個結點,但是記憶體不能釋放,因為還要用這些節點構造Huffman樹*/
		root = root->child->child;
		 
		/*對連結串列重新排序,即把新建的這個結點插入到合適的位置,使連結串列的升序不被破壞*/
		next = root;
		ListNode * parent = NULL;
		while( next != NULL  && p->huffNode.value >= next->huffNode.value )
		{
			parent = next;
			next = next->child;
		}// find location
		
		//insert
		if(parent == NULL) // Insert into start.
		{
			p->child = next;
			root = p;
		}
		else // Insert into middle or end.
		{
			p->child = next;
			parent->child = p;
		}
	}
	return &(root->huffNode);
}

/*字元-Huffman碼錶*/
string charHuffmanTable[CharCount];

/*字串棧,用來在遍歷Huffman樹時得到Huffman編碼*/
string stack;

/*樹的前序遍歷*/
void preorder(TreeNode * root)
{
// 	if(root == NULL)
// 	{
// 		stack.erase(stack.length()-2);
// 		return;
// 	}
// 	else
//	{
		//printf("%c %d\n", root->alpha, root->value);
		if(root->lChild == NULL && root->rChild == NULL)
		{
			charHuffmanTable[root->alpha] = stack;
			stack.erase(stack.length()-1);
			return;	
		}
		stack.append("0");
		preorder(root->lChild);
		
		stack.append("1");
		preorder(root->rChild);
		
		//cout << stack.length() << endl;
		if(!stack.empty())
			stack.erase(stack.length()-1);
//	}
}


//傳進來一個"10101001"的字串,返回一個對應的ASCII字元
unsigned char StrToBin(string str)
{
	int a = atoi(str.c_str());
	int b = 1;
	int ans = 0;
	while(a != 0)
	{
		ans += a%10 * b;
		b *= 2;
		a /= 10;
	}
	return (unsigned char)ans;
}

//把unsigned char型別轉換為2進位制字串
string BinToStr(unsigned char c)
{	
	string ans;
	while(c != 0)
	{
		ans.insert(ans.begin(), unsigned char(c%2 + '0'));
		c /= 2;
	}
	
	if(ans.length() < 8)
	{
		ans.insert(ans.begin(), 8-ans.length(), '0');
	}
	return ans;
}

/************************************************************************/
/*譯碼模組,返回譯出的字元,刪除string中已經用過的串                       */
/************************************************************************/ 
char decode(TreeNode * root, string & code)
{
	for(int i=0; i<code.length(); i++)
	{
		if(root->alpha == 0)
			root = (code[i]-'0') ? root->rChild : root->lChild;
		else
		{
			code.erase(0,i);
			return root->alpha;
		}
	}

	if(root->alpha != 0)
	{
		code.erase(0,i);
		return root->alpha;
	}
	code.erase();
	return '\0';
}

int main(void)
{
	/*讀取檔案並統計字元*/
	FILE * fin = fopen("huffman.in", "r");
	int c;		//這裡c不能定義為unsigned char 否則下面這個迴圈永遠都結束不了,取不了EOF(-1)
	while( (c=fgetc(fin)) != EOF )
	{
		//putchar(c);
		Ascii[c].alpha = c;
		Ascii[c].value++;
	}
	puts("");
	
	qsort(Ascii, sizeof(Ascii)/sizeof(Ascii[0]), sizeof(Ascii[0]), Comp);
	
	/*構造Huffman樹*/
	TreeNode * HuffmanRoot = CreateHuffmanTree(Ascii);//ok
	
	/*建立字元-Huffman碼錶,結果存到charHuffmanTable[]*/
	preorder(HuffmanRoot);

	//---------Debug--------打印出統計資訊----
	cout << "Char\tTimes\tCode\n";
	for(int i=0; i<CharCount; i++)
	{
		if(Ascii[i].value != 0)
		{
			cout << (char)Ascii[i].alpha << "\t" << Ascii[i].value << "\t" << charHuffmanTable[Ascii[i].alpha] << endl;
		}
	}//----------------Debug-------------------
	
	/*編碼並輸出到壓縮檔案中*/
	FILE * fout = fopen("huffman.bin","w");
	rewind(fin);	//重置檔案指標
	
	string buf;
	
	while( (c=fgetc(fin)) != EOF )
	{
		buf += charHuffmanTable[c];
		if(buf.length() >= 8)
		{
			fputc(StrToBin(buf.substr(0, 8)), fout);
			buf.erase(0, 8);
		}
		//fwrite(charHuffmanTable[c].c_str(), 1, charHuffmanTable[c].length(), fout);
	}

	int appendZero = 0;		//附加0的數量
	if(!buf.empty())
	{
		appendZero = 8 - buf.length();
		buf.append(appendZero, '0');
		fputc(StrToBin(buf.substr(0, 8)), fout);
		buf.erase(0, 8);
	}

	fclose(fin);
	fclose(fout);
	
	/*譯碼並輸出到還原檔案中*/
	fin = fopen("huffman.bin", "rb");
	fout = fopen("huffman.out", "w");
	
	/*-----------Debug----------*/
	
	/*-----------Debug----------*/
	
	//unsigned char 因為這裡有取值最高為11111111b,最小為-1,所以用int,short也可以
	int uc;
	
	/*----------------Debug-------------Rewrite---------*/
	while( (uc=fgetc(fin)) != EOF ) //!feof(fin) ) //在把檔案開啟模式改為"rb"後,完美解決
	{		
		buf += BinToStr(uc);
		//cout << buf.substr(buf.length()-8) << " ";
		//------Debug----Print------//
		//cout << buf << endl;
	}
	/*-----------Debug----------*/
	
	while(buf.length()-appendZero != 0 && !buf.empty())
	{
		//搜尋Huffman樹並譯碼
		fputc(decode(HuffmanRoot, buf),fout);
		//fputc(decode(HuffmanRoot, buf), fout);
	}
	fclose(fin);
	fclose(fout);
	printf("Time used: %d ms", clock());
	return 0;
}

程式碼寫的很亂,註釋也不完整,大神們見笑了。。。  Just a backup