哈夫曼編碼 (Huffman code)的實現,壓縮、解壓縮
阿新 • • 發佈:2019-01-25
此程式首先掃描一遍輸入檔案並統計各個字元的出現次數,然後對結果排序,再由此構造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