Huffman的應用之文件壓縮與解壓縮
文件壓縮與解壓縮>
???? 近期這段時間一直在學習樹的這樣的數據結構,也接觸到了Huffman樹以及了解了什仫是Huffman編碼,而我們經常使用的zip壓縮也是利用的Huffman編碼的特性,那仫是不是能夠自己實現一個文件壓縮呢?當然能夠了.在文件壓縮中我實現了Huffman樹和建堆Heap的代碼,zip壓縮的介紹>
http://www.cricode.com/3481.html??? 以下開始介紹自己實現的文件壓縮的思路和問題...
???? 1).統計>讀取一個文件統計這個文件裏字符出現的次數.
???? 2).建樹>以字符出現的次數作為權值使用貪心算法構建Huffman樹(依據Huffman樹的特性>字符出現次數多的一定靠近根結點,出現次數少的一定遠離根結點).
???? 3).生成Huffman編碼>規則左0右1.
???? 4).壓縮>再次讀取文件,依據生成的Huffman編碼壓縮文件.
???? 5).生成配置文件>將字符以及字符出現的次數寫進配置文件裏.
???? 6).解壓縮>利用配置文件還原出Huffman樹,依據壓縮文件還原出原文件.
???? 7).測試>推斷解壓是否正確須要推斷原文件和解壓縮之後的文件是否同樣,利用Beyond Compare軟件進行對照.
???? 以下是我舉的一個簡單的範例,模擬壓縮和解壓縮的過程,希望有讀者有幫助
?????
???? 利用Beyond Compare軟件進行對照>
????
????
???? 在實現中出現了非常多的問題,以下我提出幾個easy犯的問題,僅供參考
???? 1).在使用貪心算法構建Huffman樹的時候,假設我們以unsigned char一個字節來存儲它總共同擁有2^8=256個字符,假設將全部的字符都構建Huffman樹,這不僅減少了效率還將分配極大的內存.所以我設立了非法值
?????2).在寫壓縮文件的時候我們是將字符相應的Huffman編碼轉化為相應的位,每當填滿一個字節(8位)後再寫入壓縮文件裏.假設最後一個字節沒有填滿我們就將已經填的位移位空出後幾個位置,將未滿的位置補0補滿一個字節再寫入壓縮文件裏.
???? 3).假設我們將源文件壓縮之後再去解壓,由於你的Huffman樹和Huffman編碼都是在壓縮函數中得到的,此時再去解壓那仫你的Huffman編碼以及不存在了該怎樣去還原文件呢?這就是為什仫要生成配置文件的原因了,在配置文件裏寫入了字符以及字符出現的次數.在解壓縮中依據配置文件構建新的Huffman樹.
??? 4).由壓縮文件還原文件的時候怎樣知道壓了多少個字符呢?也就是說由於我們在壓縮的時候最後一個字節是補了0的在解壓縮的時候可能會把這個補的位當成字符的編碼來處理.一種想法是在統計字符出現的次數的時候設置一個變量,每讀取一個字符該變量就加1,最後將該變量寫進配置文件裏.第二種想法就是依據根結點的權值,通過上述簡單的實例觀察可知根結點權值中的_count就是字符出現的次數.
??? 攻克了以上幾個問題,我的程序已經能夠壓縮那256個字符並正確的還原了,那仫假設是大文件或者是漢字,圖片以及音頻視頻呢?
???? 1).由於有些特殊的字符編碼,所以我們統計字符出現的次數的時候應該用的是unsigned char,剛開始我用的文件結束標誌是EOF在ASII中它的編碼是-1此時已經不能夠用EOF來推斷是否文件結束了,所以我用了feof這個函數來推斷文件是否結束.
???? 2).統計字符出現次數應該用的類型是long long,這就攻克了大文件的壓縮和解壓縮的問題了.
???? 3).由於漢字。圖片。視頻這些在內存中是以二進制的形式存在的,所以我們將以文本形式打開讀或者寫的改動為以二進制的形式讀或者寫.
????為了驗證大文件的壓縮我找了一個8.09M的文件壓縮之後是6.50M,而且能夠正確還原.
????1).測試效率>
??????
??? 2).利用Beyond Compare軟件進行對照。假設一樣說明壓縮成功>
??????
??????
??????
????
???
????
#define _CRT_SECURE_NO_WARNINGS 1 #pragma once #include "Heap.h" template<class T> struct HuffmanTreeNode { T _weight; HuffmanTreeNode<T> *_left; HuffmanTreeNode<T> *_right; HuffmanTreeNode<T> *_parent; HuffmanTreeNode(const T& w=T()) :_weight(w) ,_left(NULL) ,_right(NULL) ,_parent(NULL) {} }; template<class T> class HuffmanTree { typedef HuffmanTreeNode<T> Node; public: HuffmanTree() :_root(NULL) {} HuffmanTree(const T* a,size_t size) :_root(NULL) { _root=_CreatHuffmanTree(a,size); } //將未出現的字符過濾出來,不構造堆 HuffmanTree(const T* a,size_t size,const T& invalid) { _root=_CreatHuffmanTree(a,size,invalid); } Node* GetRoot() { return _root; } ~HuffmanTree() { _Destroy(_root); } protected: Node *_CreatHuffmanTree(const T* a,size_t size) { struct NodeLess { bool operator()(Node *l,Node *r)const { return l->_weight < r->_weight; } }; Heap<Node *,NodeLess> minHeap; //建立結點並放入vector中 for (size_t i=0;i<size;++i) { Node *tmp=new Node(a[i]); minHeap.Push(tmp); } //取出較小的兩個結點作為左右孩子並構建父結點 while (minHeap.Size() > 1) { Node *left=minHeap.Top(); minHeap.Pop(); Node *right=minHeap.Top(); minHeap.Pop(); Node *parent=new Node(left->_weight + right->_weight); parent->_left=left; parent->_right=right; left->_parent=parent; right->_parent=parent; minHeap.Push(parent); } return minHeap.Top(); } //思路相似不帶過濾功能的 Node *_CreatHuffmanTree(const T* a,size_t size,const T& invalid) { struct NodeLess { bool operator()(Node *l,Node *r)const { return l->_weight < r->_weight; } }; Heap<Node *,NodeLess> minHeap; //建立結點並放入vector中 for (size_t i=0;i<size;++i) { if(a[i] != invalid) { Node *tmp=new Node(a[i]); minHeap.Push(tmp); } } //取出較小的兩個結點作為左右孩子並構建父結點 while (minHeap.Size() > 1) { Node *left=minHeap.Top(); minHeap.Pop(); Node *right=minHeap.Top(); minHeap.Pop(); Node *parent=new Node(left->_weight + right->_weight); parent->_left=left; parent->_right=right; left->_parent=parent; right->_parent=parent; minHeap.Push(parent); } return minHeap.Top(); } void _Destroy(Node *&root) { if(root == NULL) return ; Node *cur=root; if(cur) { _Destroy(cur->_left); _Destroy(cur->_right); delete cur; cur=NULL; return ; } } protected: Node *_root; }; void testHuffmanTree() { int a[]={0,1,2,3,4,5,6,7,8,9}; int size=sizeof(a)/sizeof(a[0]); HuffmanTree<int> ht(a,size); }
?
#define _CRT_SECURE_NO_WARNINGS 1 #pragma once //利用仿函數的特性實現代碼的復用性 template<class T> struct Small { bool operator()(const T& l,const T& r) { return l < r; } }; template<class T> struct Large { bool operator()(const T& l,const T& r) { return l > r; } }; template<class T,class Compare=Large<T>> //缺省是建小堆 class Heap { public: Heap() {} Heap(const T *a,int size) { assert(a); _a.reserve(size); for (int i=0;i<size;++i) { _a.push_back(a[i]); } //建堆的時候從倒數第一個非葉子結點開始. for (int j=(size-2)/2;j>=0;--j) { _AdjustDown(j); } } void Push(const T& x) { _a.push_back(x); _AdjustUp(_a.size()-1); } void Pop() { assert(!_a.empty()); swap(_a[0],_a[_a.size()-1]); _a.pop_back(); _AdjustDown(0); } size_t Size() { return _a.size(); } bool Empty() { return _a.empty(); } const T& Top()const { assert(!_a.empty()); return _a[0]; } void Display() { for (size_t i=0;i<_a.size();++i) { cout<<_a[i]<<" "; } cout<<endl; } protected: void _AdjustDown(int root) { int parent=root; size_t child=2*root+1; while (child < _a.size()) { Compare com; //child指向左右孩子中較大的那個數 //if (child+1 < _a.size() // && _a[child+1] > _a[child]) if(child+1 < _a.size() && com(_a[child+1],_a[child])) { child++; } //if (_a[child] > _a[parent]) if(com(_a[child],_a[parent])) { swap(_a[child],_a[parent]); parent=child; //初始的child默認指向左孩子 child=2*parent+1; } else break; } } void _AdjustUp(int child) { while (child > 0) { int parent=(child-1)/2; Compare com; //if (_a[child] > _a[parent]) if(com(_a[child],_a[parent])) { swap(_a[child],_a[parent]); child=parent; } else //插入的數據比父節點的數據域小 break; } } protected: vector<T> _a; }; //利用堆解決優先級隊列的問題 template<class T,class Compare=Large<T>> class PriorityQueue { public: PriorityQueue(int *a,int size) :_pq(a,size) {} void Push(const T& x) { _pq.Push(x); } void Pop() { _pq.Pop(); } const T& Top()const { return _pq.Top(); } void Display() { _pq.Display(); } protected: Heap<T,Compare> _pq; };
?
?
#define _CRT_SECURE_NO_WARNINGS 1 #pragma once #include "HuffmanTree.h" typedef long long Type; struct CharInfo { unsigned char _ch; //出現的字符 Type _count; //統計次數 string _code; //Huffman編碼 CharInfo(Type count=0) :_ch(0) ,_count(count) ,_code("") {} //重載相應的操作符 CharInfo operator + (const CharInfo& fc)const { return CharInfo(_count + fc._count); } bool operator != (const CharInfo fc)const { return _count != fc._count; } bool operator < (const CharInfo& fc)const { return _count < fc._count; } }; class FileCompress { public: //默認的構造函數 FileCompress() { for(size_t i=0;i<256;++i) { _infos[i]._ch=i; } } string Compress(const char *filename) { assert(filename); FILE *pf=fopen(filename,"rb"); assert(pf); unsigned char ch=fgetc(pf); //統計字符出現的次數 while (!feof(pf)) { _infos[ch]._count++; ch=fgetc(pf); } //以該字符出現的次數構建一顆HuffmanTree. CharInfo invalid; //非法值 HuffmanTree<CharInfo> ht(_infos,256,invalid); //生成Huffman編碼 string code; _CreatHuffmanCode(ht.GetRoot(),code); //_CreatHuffmanCode(ht.GetRoot()); //壓縮文件 fseek(pf,0,SEEK_SET); //回到文件頭 string compressfile=filename; compressfile += ".compress"; //壓縮後的文件名稱 FILE *fin=fopen(compressfile.c_str(),"wb"); assert(fin); size_t pos=0; //記錄位數 unsigned char value=0; ch=fgetc(pf); while (!feof(pf)) { string &code=_infos[ch]._code; for (size_t i=0;i<code.size();++i) { value <<= 1; if(code[i] == ‘1‘) value |= 1; else value |= 0; //do-nothing ++pos; if(pos == 8) //滿一個字節 { fputc(value,fin); value=0; pos=0; } } ch=fgetc(pf); } if(pos) //解決不足8位的情況. { value <<= (8-pos); fputc(value,fin); } //配置文件--便於重建Huffman樹 string configfilename=filename; configfilename += ".config"; FILE *finconfig=fopen(configfilename.c_str(),"wb"); assert(finconfig); string line; char buff[128]; for (size_t i=0;i<256;++i) { //一行一行的讀 if (_infos[i]._count) { line += _infos[i]._ch; line += ","; line += _itoa(_infos[i]._count,buff,10); line += "\n"; //fputs(line.c_str(),finconfig); fwrite(line.c_str(),1,line.size(),finconfig); line.clear(); } } fclose(pf); fclose(fin); fclose(finconfig); return compressfile; } string UnCompress(const char *filename) { assert(filename); string configfilename=filename; size_t index=configfilename.rfind("."); configfilename=configfilename.substr(0,index); configfilename += ".config"; FILE *foutconfig=fopen(configfilename.c_str(),"rb"); assert(foutconfig); string line; //讀取配置文件--獲取字符出現的次數 unsigned char ch=0; while (ReadLine(foutconfig,line)) { if(line.empty()) { line += ‘\n‘; continue; } //讀到空行 ch=line[0]; _infos[ch]._count = atoi(line.substr(2).c_str()); line.clear(); } //構建Huffman樹 CharInfo invalid; HuffmanTree<CharInfo> hft(_infos,256,invalid); //根結點的權值也就是字符出現的次數總和 HuffmanTreeNode<CharInfo> *root=hft.GetRoot(); Type charcount=root->_weight._count; //解壓縮 string uncompressfilename=filename; index=uncompressfilename.rfind("."); uncompressfilename=uncompressfilename.substr(0,index); uncompressfilename += ".uncompress"; FILE *fin=fopen(uncompressfilename.c_str(),"wb"); assert(fin); //由壓縮文件還原文件 string compressfilename=filename; FILE *fout=fopen(compressfilename.c_str(),"rb"); assert(fout); HuffmanTreeNode<CharInfo> *cur=root; int pos=7; ch=fgetc(fout); while (charcount > 0) { while (cur) { if(cur->_left == NULL && cur->_right == NULL) { //葉子結點 fputc(cur->_weight._ch,fin); cur=root; --charcount; if (charcount == 0) //全部的字符都處理完畢 break; } if (ch & (1 << pos)) //檢查字符的每一個位 cur=cur->_right; //1向右走 else cur=cur->_left; //0向左走 --pos; if(pos < 0) //一個字節解壓完畢 { ch=fgetc(fout); pos=7; } } } fclose(foutconfig); fclose(fin); fclose(fout); return uncompressfilename; } //讀取一行字符並放在line中 bool ReadLine(FILE *fout,string& line) { int ch=fgetc(fout); if(ch == EOF) return false; while (ch != EOF && ch != ‘\n‘) { line += ch; ch=fgetc(fout); } return true; } protected: //遞歸的方法求HuffmanTreeCode void _CreatHuffmanCode(HuffmanTreeNode<CharInfo> *root,string &code) { if(root == NULL) return ; _CreatHuffmanCode(root->_left,code+‘0‘); _CreatHuffmanCode(root->_right,code+‘1‘); if(root->_left == NULL && root->_right == NULL) //葉子結點 { _infos[root->_weight._ch]._code=code; } } //非遞歸求HuffmanTreeCode void _CreatHuffmanCode(HuffmanTreeNode<CharInfo> *root) { if(root == NULL) return ; _CreatHuffmanCode(root->_left); _CreatHuffmanCode(root->_right); if(root->_left == NULL && root->_right == NULL) //葉子結點 { string& code=_infos[root->_weight._ch]._code; HuffmanTreeNode<CharInfo> *cur=root; HuffmanTreeNode<CharInfo> *parent=root->_parent; while (parent) { if(parent->_left == cur) code.push_back(‘0‘); //左0 else code.push_back(‘1‘); //右1 cur=parent; parent=cur->_parent; } //編碼是從根到葉子結點的,須要逆置 reverse(code.begin(),code.end()); } } protected: CharInfo _infos[256]; }; void testFileCompress() { FileCompress fc; cout<<"開始壓縮"<<endl; cout<<"壓縮用時:"; int start=GetTickCount(); fc.Compress("2.png"); //input input.BIG 3.mp3 int end=GetTickCount(); cout<<end-start<<endl; cout<<"開始解壓"<<endl; cout<<"解縮用時:"; start=GetTickCount(); fc.UnCompress("2.png.compress"); //input.compress input.BIG.compress 3.mp3 end=GetTickCount(); cout<<end-start<<endl; } void testFileUncompress() { FileCompress fc; cout<<"開始解壓"<<endl; cout<<"解縮用時:"; int start=GetTickCount(); fc.UnCompress("2.png"); int end=GetTickCount(); cout<<end-start<<endl; }
?
????
?????經過測試這個小項目已經能夠壓縮並還原一些文件了,眼下還沒有發現什仫大的Bug,假設有童鞋發現請第一時間告訴我哦...
Huffman的應用之文件壓縮與解壓縮