哈夫曼壓縮演算法C語言實現——步驟,詳細註釋原始碼
阿新 • • 發佈:2019-01-01
哈夫曼壓縮演算法的詳細實現步驟:
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; }