1. 程式人生 > >哈夫曼壓縮演算法C語言實現——步驟,詳細註釋原始碼

哈夫曼壓縮演算法C語言實現——步驟,詳細註釋原始碼

哈夫曼壓縮演算法的詳細實現步驟:

1、定義哈夫曼樹節點,用結構體。

2、利用C語言檔案讀寫,統計字元個數。

3、根據字元個數建立哈夫曼樹(不懂haffman資料結構的自己查下資料,我這裡就不再重複了)

4、根據哈夫曼樹為每個出現的字元編碼

5、壓縮:這裡涉及到位操作,用char8位來儲存字元編碼(壓縮的關鍵)。

6、解壓縮:讀壓縮檔案,在char中從左到右按位遍歷哈夫曼樹

(再不懂的看原始碼,註釋夠意思了。)

#include<stdio.h>
#include<stdlib.h>
#include<string.h> 
//定義一個哈夫曼節點,它是個結構體
struct haffNode{
	int weight;//權重,就是這個字元出現的個數;如果這個節點是個父節點的話 ,就是兩個子節點權重的和 
	char data;//這個用來存字元本身,比如字元是'c',data='c'; 
	struct haffNode *leftChild=NULL,*rightChild=NULL;//定義左右子節點指標 
}; 
char code[256][50];//用二維陣列來儲存字元的哈夫曼編碼,其中第一維的下標表示的是這個字元的ASCII碼  
haffNode left[50];//用來儲存所有的左子節點 
haffNode right[50];//用來儲存所有的右子節點 
unsigned char saveChar = 0; //用來儲存二進位制檔案,因為char型別是1個位元組,所以每8位儲存一次 ,而且用unsigned無符號型,避免符號位干擾 
unsigned char slidChar;//定義一個字元備用,以應對可能需要的操作 
//排序函式,第一個引數是哈夫曼的節點陣列,第二個是陣列的長度,這裡用氣泡排序 
void sort(haffNode* node,int length){
	int i,j; 
	haffNode t;
	for(i=0; i<length-1; i++){
		for(j=i+1; j<length-i-1;j++){
			if(node[j].weight < node[j+1].weight){
				t = node[j];
				node[j] = node[j+1];
				node[j+1] = t;
			}
		}
	} 
} 
//構建哈夫曼樹 
void creatHaffman(haffNode *node,int length){
	if(length==1){
	return; //如果陣列長度為1,則結束遞迴,下面的就不再執行 
	}
	sort(node,length);	//將node陣列按照weight從大到小排序 
	haffNode parent;//生成父節點,因為我們的陣列從大到小排序過了,所以陣列最後面的就是最小的節點 
	left[length] = node[length-2],right[length]=node[length-1];//定義子位元組,用來存陣列最後的兩個節點 
	parent.weight = left[length].weight + right[length].weight;//父節點的權重等於兩個子節點的 權重
	//儲存兩個子節點,因為parent.leftChild是指標型別,所以賦值的時候要加& 
	parent.leftChild= &left[length];
	parent.rightChild = &right[length]; 
	//將陣列最後兩個子節點剔除,換成父節點,然後遞迴建立接下來的部分 
	node[length-2] = parent;
	creatHaffman(node,length-1); 
}
//計算字元的哈夫曼編碼 ,第一個引數是哈夫曼樹根節點,第二個引數儲存編碼的字元陣列,第三個引數是字元陣列的長度,從0開始 
void coding(haffNode *node,char *keepCode,int length){
	//如果節點沒有子節點,就說明它是葉節點,將編碼存在code數組裡 
	if(node->leftChild == NULL || node->rightChild == NULL){
		keepCode[length] ='\0';//給編碼一個終止符,形成一個完整的字串,方便拷貝,以防拷貝到之前的編碼。 
		strcpy(code[node->data-0],keepCode);//呼叫strcpy函式拷貝字串,其中code的下標用節點的字元(data)-0得到 
		return; 
	}
	keepCode[length] = '0';
	coding(node->leftChild,keepCode,length+1);
	keepCode[length] = '1';
	coding(node->rightChild,keepCode,length+1);
} 
//解壓縮  
haffNode* unzip(haffNode *node,int flag){
	if(flag == 0)
	return node->leftChild;
	if(flag == 1)
	return node->rightChild;
} 
int main(){
    int count[128]={0};//用來統計字元個數,一開始清零,其中它的下標號表示這個字元的ASCII碼
    char keepCode[50];//用於在生成編碼的時候 臨時儲存編碼 ,真正儲存編碼的地方看程式碼最上面的code[][]陣列 
    char reder;//用來存檔案中的字元 
    int fileLength=0;//用來計算原文長度 
    int zipLength=0;//用來計算壓縮文長度 
    int i; 
    int num=0;//用來計算出現的字元的個數 和其它一些計數功能 
    haffNode node[100];//用來儲存哈夫曼節點,這裡我申請100個空間事實上不需要那麼大,大概需要26+26+20(26個英文字母大小寫+標點符號) 
	FILE *fpr = fopen("E:\\input.txt","r");//讀入檔案,其中input.txt的路徑你要自己設定,可以自己建立一個文字,寫一些英文進去 
	FILE *fpw = fopen("E:\\output1","wb");//寫入檔案,wb是寫入二進位制檔案,路徑設定隨意,但是要符合格式,寫入的時候檔案自動生成。
	FILE *fpr1= fopen("E:\\output1","rb");;//用於解壓縮時讀入檔案 ,rb是讀入二進位制檔案 
	FILE *fpw1 = fopen("E:\\output3.txt","w"); //用於解壓縮時寫入檔案 
	//解壓需要用的 
	char op;
	haffNode *z;
	//讀取檔案 
	while((reder=fgetc(fpr))!=EOF){//一個一個地讀入字元 
		fileLength ++;//每讀進一個字原文長度+1 
		count[reder-0]++;//reder-0可以得到字元的ASCII碼,然後累加統計 
	} 
	//迴圈陣列,因為ASCII表中有255個字元,所以陣列中有些字元是完全沒有出現過的,我們要將出現過的存在charNode數組裡。 
	for(i=0; i<128; i++){
		if(count[i]!=0){
			node[num].data=i;//之前說過,下標就是出現的字元的ASCII碼
			node[num].weight=count[i];//count[i]存的就是字元出現的次數
			num++;//計數加1 
		}
	}
	//構建哈夫曼樹
	creatHaffman(node,num); 
	//計算哈夫曼編碼
	coding(&node[0],keepCode,0); 
	//根據哈夫曼編碼把原來的文字壓縮儲存 
	//讀取檔案 
	num=0;//計數 
	fseek(fpr,0L,0);//因為上面已經讀過檔案了,fpr指標已經向下移動,所以這邊使用 fseek函式將指標復原到離0(檔案起始位置)0L(第0個位元組)處 
	while((reder=fgetc(fpr))!=EOF){ //一個一個地讀入字元 
		for(i=0; i<strlen(code[reder-0]); i++){
			saveChar |= code[reder-0][i]-'0';//讓saveChar和編碼中的每一位進行或操作,用字元的'1'-'0',就可得到0000 0001.     
			num++;
			if(num == 8){
				fwrite(&saveChar,sizeof(char),1,fpw);//每8位寫入一次檔案
				zipLength++; 
				saveChar = 0;//重新置0 
				num=0;
			} 
			else{
				saveChar = saveChar << 1	; //每做完一步,向左移一位  
			}
		}
	} 
	//如果最後剩餘的編碼不到8位,將其移到最左端
	if(num != 8){
		saveChar = saveChar<<(8-num);//移到最左端 
		fwrite(&saveChar,sizeof(char),1,fpw);
		zipLength++;
	} 
	fclose(fpr);
	fclose(fpw);
	//根據哈夫曼編碼解壓縮,主要思想是根據編碼遍歷哈夫曼樹 
	num=0;//計算解壓縮後的檔案長度 
	z = &node[0];
	while(fread(&reder,sizeof(char),1,fpr1)){
		//如果解壓縮的長度等於原文長度,停止解壓縮。為什麼多這一個條件,因為在編碼壓縮的時候可能出現剩餘的編碼不足8位的情況,不足8位不成8位後,後面的補位可能造成干擾 
		if(fileLength == num){
			break;
		} 
		op = 128;//1000 0000
		for(i=0; i< 8; i++){
		slidChar = reder & op;
		reder = reder << 1;
		slidChar = slidChar >>7;
		z = unzip(z,slidChar-0);
		if(z->leftChild == NULL || z->rightChild == NULL){
			fprintf(fpw1,"%c",z->data);
			num++;//每寫進一個字元+1 
			z = &node[0];
		}
	}
	}
	fclose(fpr1);
	fclose(fpw1);
	//計算壓縮率	
	printf("原檔案:%dK\n",fileLength/1024+1);
	printf("壓縮完成!請檢視output1:%dK\n",zipLength/1024+1); 
	printf("解壓縮完成!請檢視output3.txt:\%dK\n",fileLength/1024+1); 
	printf("壓縮率:%.2f%%\n",(float)(fileLength-zipLength)/fileLength*100);	
    return 0;
    }