信息论实验-信源编码算法 (Huffman and Shannonn Fano编码C++实现)
實驗目的
1. 實現壓縮編碼算法——Huffman編碼 2. 實現壓縮編碼算法——Shannon Fano編碼 3. 實現壓縮編碼算法——LZ編碼 4. 實現壓縮編碼算法——算數編碼 5. 利用上述壓縮算法壓縮圖像、音頻、視頻文件,分析壓縮算法的性能。** 先上源代碼,如果對實驗的源代碼感興趣的同學,請到小豬嘎嘎的倉庫下載**
信源編碼源代碼
第一章:Huffman編碼的實現
Huffman編碼原理
數據壓縮是一門通信原理里和計算機科學都會涉及到的學科,在通信中,一般稱為信源編碼,在計算機科學里,通常稱為數據壓縮,兩者沒有本質區別,從數學角度看,都是映射。壓縮可以分為有損壓縮和無損壓縮。有損壓縮,指的是壓縮之后無法還原原始信息,但是可以達到很高的壓縮率,主要應用于視頻、通話、圖像等信息的傳輸領域。無損壓縮則用于文本文件等必須完整還原信息的領域。
Huffman編碼是一種可變長編碼(VLC:ariable length coding)方式,于1952年由huffman提出。依據字符在需要編碼文件中出現的概率提供對字符的唯一編碼,并且保證了可變編碼的平均編碼最短,被稱為最優二叉樹,有時又稱為最佳編碼。
Huffman編碼過程
統計各個字符出現的頻率
當談到統計字符這里,經過了一番折騰后在這里我有很多想要總結的,對于數學專業的學生來講我覺得這里是一個很難跨越的坎,為什么呢?原因在于這里涉及到計算機的儲存原理,數據的讀取,存儲操作,這些操作都非常接近計算機底層,就拿數據的存儲來說,首先我們要搞明白什么是ASCII文件,什么是二進制文件,這兩類文件讀取有什么差別,在計算機里又是如何存儲的,這里參考網上博客的內容多多理解才好,這里我折騰了很久,首先搞明白ASCII和二進制文件這兩個概念(概念很重要),然后分析差別,最后學習C++提供的API,比較兩者的不同。而我們數學院的同學接觸計算機也不少,但是接觸底層的人很少,所以當談到一些數據結構、文件存儲、硬件編程等等就會趕腳很無助,因為平時大家最多的就是用Matlab實現一些算法,對底層的接觸的很少。當然Huffman算法也可以由Matlab來實現,我也看到網上有一些實例程序,但怎么說呢,如果用Matlab實現了Huffman我只能說,我學會了Huffman算法,但是不能說我學會了Huffman編碼。因為數據編碼這東西最直接聯系的就是數據傳輸,我們需要知道計算機是怎樣實現文件壓縮、傳輸的過程的,就必須深入底層了解它的本質。
數據的本質:0和1
在我弄不明白Huffman編碼的時候,我就問自己這樣一個問題:Huffman編碼是用來做什么的?信息論教科書上、數據結構教科書、各種Huffman編碼的解釋,Huffman 編碼是一種編碼方式,是一種用于無損數據壓縮的熵編碼(權編碼)算法等等各種解釋感覺很厲害的樣子。聽著好像懂了,但又好像沒懂。但是一邊看別人代碼,一邊寫自己代碼的過程,我變得有些心虛,心想我這是在做什么? 然后停下敲打鍵盤,想了一些問題。別擔心,絕對不是高大上的問題,都是特別特別弱智的問題,我說的是特別,對,老師您沒聽錯。
1. 什么是 ”編碼” ? 是的沒錯,就是這么弱智的問題,學了這么久信息論、C語言和數據結構我的確問了自己一個這么弱智的問題。似乎很簡單,但往往這么簡單的問題我們都忽略去思考了,最后發現:哦,原來我一直干的是這樣一件事。
2. 什么是ASCII編碼?什么是ASCII編碼,好像大家都知道到0的ASCII值是48,A打得ASCII值是65,即便不知道每一個字符的ASCII值,也知道在任何一本C語言教材的最后幾頁肯定有一張印有ASCII編碼的。具體什么是ASCII我在這里就不介紹了,網上有很多解釋,教科書也有很多,關鍵是理解。
3. Huffman為什么會出現?我覺的這個問題也弱智的很,但是這個問題在我寫Huffman編碼過程給了我很大的幫助。答案很簡單:為了壓縮數據。那么數據是什么?計算機里數據的本質就是0和1,那么怎么壓縮0和1?當然0和1不能壓縮,壓縮的是0和1的組合。0和1的組合代表的是什么? 組合代表的是信息,不同的組合代表不同的信息。回過頭去想ASCII編碼,ASCII是定長編碼,原來Huffman編碼實現的是傳輸同樣信息的情況下,盡量使平均碼長變得最小,這樣就實現了數據壓縮的目的。
回答完上面這些問題我才真正著手開始進行編程。
上面說道數據的讀取和存儲是一個很難繞過去的坎,那么到底怎么繞過去?剛開始我在糾結一個txt文檔和mp4文件在數據讀取時有什么不同嗎?答案是:有。txt文檔就是我們講的ASCII文件,每8bit一個字符,而mp4文件本質上又是圖像文件。這里真的很難繞過來,因為以前接觸過的C語言API大部分是讀取字符或字符串,很少用二進制的方式讀取。但是當這樣想后就明白了:所有文件在計算機里存儲的都是0和1。編碼的時候不用關心每個字符占多少bit,按照自己的編碼方式編,只要能恢復0和1數據即可。所以,所有數據都通過二進制的方式讀取,以二進制的方式存儲,這樣就沒有必要擔心文件類型所帶來的困惑了。
具體字符統計這部分我寫了一個count()函數。函數的輸入是一個文件的地址,輸出是文件中各個字符的統計向量。當然這個待壓縮文件不能過大,因為通過讀zip實現原理博客中我發現rar和zip等壓縮軟件之所以那么快是進行了很多優化,以我現在的時間和能力還搞不懂里面的一些東西,所以我寫的代碼運行很慢,但是結果是正確的,壓縮效果也很好。函數具體的實現在后面算法和性能分析部分講。
### 創建Huffman樹(核心)
根據字符的頻率按Huffman算法構建Huffman算法創建Huffman樹
步驟如下:
把這些節點作為帶權值的二叉樹的根節點,左右子樹為空
選擇兩棵根結點權值最小的樹作為左右子樹構造一棵新的二叉樹,且至新的二叉樹的根結點的權值為其左右子樹上根結點的權值之和。
把權值最小的兩個根節點移除
將新的二叉樹加入隊列中。
樹的建立過程是從葉子節點往上走,直到根節點。
建樹過程如下圖所示
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-MmzD5hDX-1584452720736)(http://picture.piggygaga.top/InformationTheory/second/CreateTRee.png “CreateHuffmanTree”)]
綠色橢圓表示葉子節點,棕色節點表示根節點,藍色橢圓表示中間節點,首先從最底層的葉子節點開始創建樹,然后向上走,建樹過程中每次建完樹的節點信息要從數組中刪除節點信息,方便后面的建樹操作。每次刪除兩個節點會建立一個新的合并節點,然后重新按頻率從小到大排序,執行同樣的操作直到最后剩下根節點,將根節點地址保存下來,在下一步壓縮數據時使用。
經過此步驟,每個字符對應的Huffman編碼都可以得到。我們可以將編碼信息保存下來,類似ASCII編碼表一樣,我們把Huffman編碼表也保存下來。
Huffman編碼
根據Huffman 樹實現編碼并將編碼結果和要編碼的數據建立映射關系,存儲的壓縮文件需要添加頭文件,用于解碼使用。
重新從文件中讀取信息,此時不需要進行統計字符頻率,只需要在Huffman表中找到對應的Huffman編碼然后將編碼壓入壓縮文件中。
壓縮文件包括兩部分:頭文件,數據部分。壓縮文件的結構見下圖
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-1oeNATPi-1584452720737)(http://picture.piggygaga.top/InformationTheory/second/Huffmanfile.PNG “壓縮文件內容”)]
壓縮數據首先以一個>開始表示這是一個Huffman壓縮數據,這樣做的目的是在解壓過程會判斷這是否是一個Huffman編碼文件,如果沒有這個標識,認定該文件不具備Huffman編碼文件的特性性,也就沒有辦法解壓。第二部分是頭文件,存儲著字符頻數,這樣做也是為了解壓方便,解壓過程和壓縮過程是兩個獨立的過程,將字符頻數存進頭文件,在解壓時需要利用頭文件字符頻數重新建立一棵Huffman樹,和壓縮過程的操作過程是一樣的。最后通過讀取源文件的字符,在Huffman編碼表中找到對應的字符編碼,將字符編碼壓進數據部分,因為Huffman編碼是異字頭碼,所以不需要做分隔,把所有數據壓進去即可。
Huffman 解碼
根據獲取的Huffman碼來逆向獲取編碼信息,而且從解壓文件中一次性獲取的數據是一個很長的字符串,這個串是壓縮后的huffman編碼,實際上是機器碼。
解碼過程
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-G3OeNeIZ-1584452720738)(http://picture.piggygaga.top/InformationTheory/second/decode.png “解碼過程”)]
解碼過程共分為3部分,首先程序讀取壓縮文件的頭部獲取字符頻數,并據此構建Huffman樹,其次讀取數據部分的Huffman編碼,并根據Huffman樹得到編碼的字符值,最后將字符壓入解壓文件中,如果讀到文件結尾,結束解碼過程。
Huffman編碼算法C++實現和性能分析
編程語言:C++
編程環境:Visual studio 2015
本次實驗我采用面向對象的編程思想,每種算法建立一個對象,Huffman編碼我建立了一個Huffman類。所有的編碼操作都是基于這個類。
Huffman類如下所示
程序調用過程中只會用到公有屬性和公有函數,所以下面依次介紹公有屬性和公有函數的功能。
主函數部分如下圖:
文件壓縮步驟:
第一步:讀入待壓縮的文件名
第二步:建立huf對象為Huffman類型
第三步:cout()函數計算各個字符頻數
第四步:CreatehuffmanTree()建立Huffman樹
第五步:GetHuffmanCode()獲取Huffman編碼
第六步:WriteCode()壓縮文件
第七步:Decode() 解碼
原始文本為一個名為haha.txt的文本文檔,該文檔大小為4096 bytes
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-PgHukUKC-1584452720738)(http://picture.piggygaga.top/InformationTheory/second/hahaSource.PNG “文本文檔”)]
壓縮后的文件為一個二進制文件,用二進制查看軟件打開后是亂碼文件
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Py35jVaH-1584452720739)(http://picture.piggygaga.top/InformationTheory/second/yasuo.PNG “二進制查看器查看”)]
文件大小為10385Bytes,等等,10385Bytes,為啥變大了?不是壓縮么?
這個現象后面解釋,下面繼續看解碼效果。
解碼文件為out.txt文件
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-4r9tvk5o-1584452720739)(http://picture.piggygaga.top/InformationTheory/second/hahaSource.PNG “解碼文件”)]
我們可以看到解碼文件和原始文件一樣,所以正確性沒有任何問題。
上面說道壓縮后的文件反而比原來的文件大,其實這并不奇怪,因為壓縮文件比原始文件多了數據頭部,head部分也是會占用一定的空間的,所以才會產生這種情況,所以Huffman編碼不適合文件很小的數據壓縮,數據要大一些才會有明顯差異。經過實驗測試文件大于1M后才會有壓縮效果。
測試圖片是功夫熊貓的一張圖片
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ZBNkfDjh-1584452720740)(http://picture.piggygaga.top/InformationTheory/second/Abao.jpg “待壓縮圖像”)]
圖片大小為47kb,壓縮后的圖像為48kb,因為圖片太小所以還是沒有明顯的壓縮效果。圖像文件之所以壓縮效果不好是因為圖像格式本身已經是經過壓縮后的文件,已經用Huffman壓縮算法或其他壓縮算法壓縮過了,不同的圖像格式有不同的壓縮算法,一般通常是集中算法混合使用,所以根據信息論理論,當壓縮的文件接近最佳壓縮比時此時無論怎樣做都無法進行更優的無損壓縮了,注意這里必須是無損壓縮,有損壓縮還是可以繼續進行的,這里Huffman是一種無損壓縮算法。
壓縮用時:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-LAZnmIZJ-1584452720740)(http://picture.piggygaga.top/InformationTheory/second/KongFutime.PNG “壓縮用時”)]
總共有256個字符被統計出來,壓縮數據耗時25000ms。解碼用時幾乎為0ms,所以可以看出解碼過程不需要統計字符頻數,速度相當快。
視頻壓縮的是《當愛已成往事》mv
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-x5vygBPR-1584452720741)(http://picture.piggygaga.top/InformationTheory/second/Love.PNG “love”)]
文件大小為7.82kb,壓縮后的文件大小為:7.81壓縮率為99.8%
綜上;程序可以對所有文件進行壓縮,當原始文件小于7M時,壓縮文件大于原始文件,并不能實現壓縮效果,當文件達到13M時壓縮比率可達到77%,且當文件越大壓縮效果越好
Huffman算法實現過程中的心得體會
解碼過程出現了一些問題,開始出現文件錯誤的信息,后來調試發現root節點沒有賦予初始的空間,使得程序崩潰。后來解決這一問題后又出現了解碼文件亂碼的現象,這個問題真的是很坑,后來找到原因是:編碼和解碼過程中建的樹不一致,導致編碼和解碼的字符編碼不能對應產生了亂碼現象。現在程序可以完成獨立的編碼,獨立的解碼工作。總結一下Huffman編碼,通過程序實現的Huffman可以達到無損壓縮的目的,且文件越大壓縮的效果越好,當文件大于7M左右時可以達到壓縮的目的,文件小于7M左右時并不能達到壓縮目的。文件很大時可以達到很高的壓縮比例。但Huffman有一個很致命的缺點:很耗時。每次編碼都要統計字符頻數,在統計字符頻數的時候要遍歷一次文件,然后利用字符頻數向量建立Huffman樹,此后還要遍歷一次文件來壓縮文件,所以Huffman編碼非常耗時,主要時間花費在統計字符頻數那里。
第二章:Shannon Fano編碼
一. Shannon Fano編碼原理
Fano編碼和Huffman編碼稍有不同,它不是最佳編碼方法,但有時也可以得到最佳的性能。Huffman是由葉子節點合并構建Huffman樹,利用的是合并的思想,而Fano編碼方法正好相反,Fano編碼是從整體進行分割,到最后葉子節點結束。
首先,將信源符號以概率低賤的次序排列起來,將排列好的信源符號劃分成兩大組,使魅族的概率和近于相同,并各賦予一個二元嗎符號‘0’和‘1’.然后,將每一大組的信源符號再分成兩組,使同一組的兩個小組的概率近于相同,并分別賦予-個二元碼符號。依次下去,直至每個小組只剩一個心愿符號為止。最后,由前向后讀取碼符號序列。這樣,信源符號所對應的碼符號序列則為編得的碼字。
二. Shannon Fano 編碼過程
和Huffman編碼類似Shannon Fano 編碼同樣也要經過字符統計。
這里是和Huffman編碼不一樣的地方,Huffman是由葉子節點向樹根的方向逐步構造Huffman編碼樹,而Shannon Fano 編碼是從樹根逐漸拆分執行遞歸操作,最后生成一顆編碼樹。
二. Shannon Fano 編碼的實現和性能分析
Shannon Fano編碼通過建立一個Fano類實現。
下面是Fano類的具體定義如下:
關鍵屬性:
關鍵函數:
壓縮比例
| | | | |
|:-😐:-😐:-😐:-😐
|原始文件 |890Bytes(文本)|46.7kb(圖片) |7.82M(視頻)|
|壓縮文件| 1146Bytes| 47.9kb |7.81M|
|壓縮率 |1.29 |1.025| 99.8%|
|壓縮用時 |0/s |27/s| 6400/s|
|解壓用時| 0/s |1/s |2400/s|
由于程序設置問題,當文件大于13M后會堆棧溢出,這里后續需要調整程序進行優化。
三. Shannon Fano編碼總結
Shannon Fano編碼和Huffman編碼比較類似,編碼過程也沒有太大區別。唯一的區別在結果。Shannon Fano相比于Huffman編碼,其壓縮速率沒有差異,原因在于兩者都需要知道每種字符的概率信息,所以在編碼之前必須統計每種字符的頻數。另一方面,Shannon 平均壓縮比例要比Huffman的低一些,雖然有時Shannon Fano可以達到最優編碼,但是大部分情況是不能達到的,所以其平均壓縮比例要略低于Huffman編碼。
由于篇幅有限,下一篇博客介紹LZ編碼和算數編碼,最后附上Huffman編碼和Fano編碼的源代碼
HuffmanClass.h
#include <iostream> #include <vector> #include <fstream> #include <algorithm> using namespace std; class Huffman { public:struct HuffmanNode{unsigned char value; //節點值int frequency = 0; //節點頻數struct HuffmanNode *Lchild = NULL;struct HuffmanNode *Rchild = NULL;}; private:struct CountVector{unsigned char value; //字符int frequency = 0; //字符頻數struct HuffmanNode *nodeAddress = NULL;};struct HuffmanCode{unsigned char value;int frequency = 0;string code;int codelen;};//根節點static bool mysortfunction(CountVector A, CountVector B){ //用于sort排序算法return A.frequency < B.frequency;} public:HuffmanNode *root;string fileAddress;long int NumOfChar = 0;vector<CountVector> charCountFrequency; //用于存儲字符頻數vector<HuffmanCode> HuffmanCodeVec;Huffman(string sourcefile); //構造函數void count(); //統計各個字符的頻數函數void CreateHuffmanTree(vector<CountVector> charFrequency); //創建huffman樹void GetHuffmanCode(HuffmanNode *root, int len);void WriteCode(vector<HuffmanCode> hfCode);void Decode(string sourcefile, string dstfile); };Huffman::Huffman(string sourcefile) {fileAddress = sourcefile; //初始化文件讀入地址 }void Huffman::count() {ifstream readfile;readfile.open(fileAddress, ios::in | ios::binary);unsigned char *now = new unsigned char; //存儲當前讀取到的字符while (!readfile.eof()){CountVector *presentChar = new CountVector; //讀取到的字符信息readfile.read((char*)now, sizeof(unsigned char));int flag = 0; //標志是否是新出現的字符for (int i = 0; i < charCountFrequency.size(); i++){if (*now == charCountFrequency[i].value){charCountFrequency[i].frequency++;NumOfChar++;flag = 1;}}if (flag == 0){presentChar->value = *now;presentChar->frequency++;NumOfChar++;charCountFrequency.push_back(*presentChar);}}readfile.close(); } void Huffman::CreateHuffmanTree(vector<CountVector> charFrequency) {vector<CountVector> buildtree;//HuffmanNode newNode;HuffmanNode *rootnode = new HuffmanNode;buildtree = charFrequency;sort(buildtree.begin(), buildtree.end(), mysortfunction);int treedepth = 0;while (buildtree.size() > 1){HuffmanNode *nodeLeft = new HuffmanNode,*nodeRight = new HuffmanNode,*newNode = new HuffmanNode;CountVector insertnew;if (buildtree[0].nodeAddress != NULL){ //如果是葉子節點的話 左右子樹的地址都為NULLnodeLeft->Lchild = buildtree[0].nodeAddress->Lchild;nodeLeft->Rchild = buildtree[0].nodeAddress->Rchild;}else{nodeLeft->Lchild = NULL;nodeLeft->Rchild = NULL;}if (buildtree[1].nodeAddress != NULL){nodeRight->Lchild = buildtree[1].nodeAddress->Lchild;nodeRight->Rchild = buildtree[1].nodeAddress->Rchild;}else{nodeRight->Lchild = NULL;nodeRight->Rchild = NULL;}nodeLeft->frequency = buildtree[0].frequency;nodeLeft->value = buildtree[0].value;nodeRight->frequency = buildtree[1].frequency;nodeRight->value = buildtree[1].value;newNode->frequency = nodeRight->frequency + nodeLeft->frequency;newNode->Lchild = nodeLeft;newNode->Rchild = nodeRight;insertnew.frequency = newNode->frequency;insertnew.value = 0;insertnew.nodeAddress = newNode;//vector<CountVector>::iterator it = buildtree.begin();buildtree.erase(buildtree.begin());//vector<CountVector>::iterator it = buildtree.begin();buildtree.erase(buildtree.begin());//vector<CountVector>::iterator it = buildtree.begin();buildtree.insert(buildtree.begin(), insertnew);sort(buildtree.begin(), buildtree.end(), mysortfunction); //每次更新完要排序rootnode = newNode;treedepth++;}//cout << treedepth;root = rootnode; } void Huffman::GetHuffmanCode(HuffmanNode* root, int depth) {static char code[512];//判斷左兒子if (root->Lchild != NULL){code[depth] = '0';code[depth + 1] = '\0';GetHuffmanCode(root->Lchild, depth + 1);}if (root->Rchild != NULL){code[depth] = '1';code[depth + 1] = '\0';GetHuffmanCode(root->Rchild, depth + 1);}else{HuffmanCode insertCode;int codelength = 0;for (int i = 0; i < charCountFrequency.size(); i++){if (root->value == charCountFrequency[i].value){insertCode.code = code;insertCode.value = charCountFrequency[i].value;insertCode.frequency = charCountFrequency[i].frequency;}}for (int j = 0; code[j] != '\0'; j++){codelength++;}insertCode.codelen = codelength;HuffmanCodeVec.push_back(insertCode);} } void Huffman::WriteCode(vector<HuffmanCode> HFCode) {//從文件總讀取字符并進行編碼int codeNum = HFCode.size();string address = fileAddress;ofstream writecode;ifstream read;read.open(address, ios::in | ios::binary); //讀入文件writecode.open(address + ".dada", ios::out | ios::binary); //以*.dada命名unsigned char *now = new unsigned char; //讀取的 當前字符unsigned char save = 0; //每一次保存一個字節的長度int len = 0;long int totalLen = 0; //文件編碼總長int last; //最后寫入時的位數for (int i = 0; i < HFCode.size(); i++){totalLen = totalLen + HFCode[i].codelen;}last = totalLen % 8;// 將Huffman編碼寫入頭部,當作頭文件方便譯碼操作。char head = '>';writecode.write((char*)&head, sizeof(char));writecode.write((char *)&codeNum, sizeof(int));writecode.write((char *)& last, sizeof(int)); //寫入最后一次寫入的位數for (int i = 0; i < codeNum; i++){ //寫入字符值和頻數writecode.write((char*)&charCountFrequency[i].value, sizeof(unsigned char));writecode.write((char*)&charCountFrequency[i].frequency, sizeof(int));}//read.read((char*)now, 1);read.read((char*)now, sizeof(unsigned char));while (!read.eof()){int flag = 0;for (int i = 0; i < HFCode.size(); i++){if (*now == HFCode[i].value){flag = 1;for (int j = 0; j < HFCode[i].codelen; j++){if (len != 0)save = save << 1;save = save | (HFCode[i].code[j] - '0');len++;if (len == 8){writecode.write((char *)&save, sizeof(unsigned char));save = 0;len = 0;}}}}if (flag == 0){cout << *now << "沒在表中找到" << endl;}read.read((char*)now, sizeof(unsigned char));//*now = read.get();}if (len != 0){writecode.write((char*)&save, sizeof(unsigned char));}read.close();writecode.close();} void Huffman::Decode(string sourcefile, string dstfile) {ifstream read;ofstream write;vector<CountVector> arr;unsigned char now; //讀取的當前字符int last = 0; //最后一次讀取的位數int n; //字符種數read.open(sourcefile, ios::in | ios::binary); //讀取解碼文件write.open(dstfile, ios::out | ios::binary); //打開解碼后的文件read.read((char*)&now, sizeof(now));if (!(now == '>')){cout << "該文件的Huffman編碼格式不正確" << endl << endl;read.close();return;}read.read((char*)&n, sizeof(int));read.read((char*)&last, sizeof(last));for (int i = 0; i < n; i++){CountVector *insert = new CountVector;read.read((char*)&(insert->value), sizeof(unsigned char));read.read((char*)&(insert->frequency), sizeof(int));arr.push_back(*insert);}this->root = new HuffmanNode;CreateHuffmanTree(arr);GetHuffmanCode(root, 0);HuffmanNode *pNow = root;unsigned char *temp = new unsigned char; //每次讀一個字節read.read((char*)temp, sizeof(unsigned char));while (!read.eof()){unsigned char *ifLast = new unsigned char; //用于判斷是否讀到文件末尾read.read((char*)ifLast, sizeof(unsigned char));int i;if (read.eof()){i = last - 1;}else{i = 7;}for (; i >= 0; i--){if ((*temp >> i & 1) == 0) //向右移動7位判斷讀出的是0 還是1 pNow = pNow->Lchild;elsepNow = pNow->Rchild;if (pNow->Lchild == NULL && pNow->Rchild == NULL){write.write((char*)&(pNow->value), sizeof(unsigned char));pNow = root;}}temp = ifLast;}read.close();write.close(); }HuffmanMain.cpp
#include <string> #include "huffmanClass.h" #include <time.h> int main() {clock_t start, end, start1, end1;cout << "!-------------Huffman壓縮編碼---------!" << endl << endl;cout << "!--------------作者:小豬嘎嘎------------!" << endl << endl;cout << "!--------------壓縮程序----------------! " << endl << endl;cout << "!--------------csdn-------! " << endl << endl;string filePath;cout << "請輸入待編碼文件地址" << endl << endl;getline(cin, filePath);Huffman huf(filePath);start = clock();huf.count(); //獲取字符頻數存在charCountFrequency數組中cout << huf.charCountFrequency.size() << endl;//getchar();huf.CreateHuffmanTree(huf.charCountFrequency);huf.GetHuffmanCode(huf.root, 0);huf.WriteCode(huf.HuffmanCodeVec);end = clock();cout << "壓縮使用時間為 : " << double((end - start) / CLOCKS_PER_SEC) * 1000 << " /ms" << endl << endl;cout << "!--------------解碼程序------------!" << endl << endl;//cout << "!--------------請輸入待解碼的文件--------------!" << endl << endl;//string outfilePath;//getline(cin, outfilePath);//Huffman hufdecode(outfilePath);//huf.root = new Huffman::HuffmanNode;start1 = clock();Huffman hufde(filePath);hufde.Decode(filePath + ".dada", "./Out/" + filePath);end1 = clock();cout << "解碼使用時間為 : " << double((end1 - start1) / CLOCKS_PER_SEC) * 1000 << " /ms" << endl << endl; `getchar(); }ShannonFano.h
#include <iostream> #include <vector> #include <fstream> #include <algorithm> using namespace std; class Fano { public:struct FanoNode{unsigned char value; // 字符struct FanoNode *Lchild = NULL; //左孩子struct FanoNode *Rchild = NULL; //右孩子}; private:struct CountVector{unsigned char value;int frequency = 0;struct FanoNode *nodeAddress = NULL;}; private:struct FanoCode{unsigned char value;int frequency;string code;int codelen;}; private:static bool mysortfunction(CountVector A, CountVector B){ //排序函數return A.frequency > B.frequency;} public:FanoNode *root; //存儲樹的結構string fileAddress;long int NumOfChar;vector<CountVector> charFrequency; //字符頻率vector<FanoCode> FanoCodeVec; //存儲Fano碼, 包括碼長,碼字Fano();void count();void open(string add);void CreateTree(vector<CountVector> charFrequency, FanoNode *rootNode);void GetFanoCode(FanoNode* root, int depth);void WriteCode(vector<FanoCode> HFCode); void Decode(string sourcefile, string dstfile); private:void splitVec(vector<CountVector> charFr, vector<CountVector> &charFr1, vector<CountVector> &charFr2); }; Fano::Fano() {NumOfChar = 0; } void Fano::open(string add) {fileAddress = add; } void Fano::count() {ifstream readfile;readfile.open(fileAddress, ios::in | ios::binary);unsigned char *now = new unsigned char; //′?′¢μ±?°?áè?μ?μ?×?·?while (!readfile.eof()){CountVector *presentChar = new CountVector; //?áè?μ?μ?×?·?D??¢readfile.read((char*)now, sizeof(unsigned char));int flag = 0; //±ê??ê?·?ê?D?3???μ?×?·?for (int i = 0; i < charFrequency.size(); i++){if (*now == charFrequency[i].value){charFrequency[i].frequency++;NumOfChar++;flag = 1;}}if (flag == 0){presentChar->value = *now;presentChar->frequency++;NumOfChar++;charFrequency.push_back(*presentChar);}}readfile.close(); } void Fano::CreateTree(vector<CountVector> charFr, FanoNode *rootNode) {vector<CountVector> buildtree = charFr;if (buildtree.size() == 1){//root->Lchild = new FanoNode;//root->Rchild = new FanoNode;rootNode->Lchild = NULL;rootNode->Rchild = NULL;rootNode->value = buildtree[0].value;}else{sort(buildtree.begin(), buildtree.end(), mysortfunction);vector<CountVector> charFr1, charFr2;splitVec(buildtree, charFr1, charFr2);rootNode->Lchild = new FanoNode;CreateTree(charFr1, rootNode->Lchild);rootNode->Rchild = new FanoNode;CreateTree(charFr2, rootNode->Rchild);rootNode->value = 0;}return; } void Fano::splitVec(vector<CountVector> charFr, vector<CountVector> &charFr1, vector<CountVector> &charFr2) {int length = charFr.size();if (length == 1){cout << "拆分的數組長度不夠" << endl;}long int NumOfCharf = 0;for (int i = 0; i < length; i++){NumOfCharf = NumOfCharf + charFr[i].frequency;}double ratio = 0;int splitIndex = 0; //切割處的索引for (int i = 0; i < length; i++){ratio = ratio + double(charFr[i].frequency / NumOfCharf);if (ratio > 0.5){if (i > 0){splitIndex = i - 1;break;}else{splitIndex = i;break;}}}for (int i = 0; i < splitIndex + 1; i++){charFr1.push_back(charFr[i]);}for (int i = splitIndex + 1; i < charFr.size(); i++){charFr2.push_back(charFr[i]);} } void Fano::GetFanoCode(FanoNode* root, int depth) {static char code[512];//?D??×ó?ù×óif (root->Lchild != NULL){code[depth] = '0';code[depth + 1] = '\0';GetFanoCode(root->Lchild, depth + 1);}if (root->Rchild != NULL){code[depth] = '1';code[depth + 1] = '\0';GetFanoCode(root->Rchild, depth + 1);}else{FanoCode insertCode;int codelength = 0;for (int i = 0; i < charFrequency.size(); i++){if (root->value == charFrequency[i].value){insertCode.code = code;insertCode.value = charFrequency[i].value;insertCode.frequency = charFrequency[i].frequency;}}for (int j = 0; code[j] != '\0'; j++){codelength++;}insertCode.codelen = codelength;FanoCodeVec.push_back(insertCode);} } void Fano::WriteCode(vector<FanoCode> HFCode) {//讀取文件并寫入數據int codeNum = HFCode.size();string address = fileAddress;ofstream writecode;ifstream read;read.open(address, ios::in | ios::binary); //以二進制方式讀取writecode.open(address + ".dada", ios::out | ios::binary); //以二進制方式寫入unsigned char *now = new unsigned char; //存儲字符值unsigned char save = 0; //保存當前字符int len = 0;long int totalLen = 0; //總長int last; //結尾字符長度for (int i = 0; i < HFCode.size(); i++){totalLen = totalLen + HFCode[i].codelen;}last = totalLen % 8;//char head = '>';writecode.write((char*)&head, sizeof(char));writecode.write((char *)&codeNum, sizeof(int));writecode.write((char *)& last, sizeof(int)); //D′è?×?oóò?′?D′è?μ???êyfor (int i = 0; i < codeNum; i++){ //D′è?×?·??μoí?μêywritecode.write((char*)&HFCode[i].value, sizeof(HFCode[i].value));writecode.write((char*)&HFCode[i].frequency, sizeof(HFCode[i].frequency));}//read.read((char*)now, 1);read.read((char*)now, sizeof(unsigned char));while (!read.eof()){int flag = 0;for (int i = 0; i < HFCode.size(); i++){if (*now == HFCode[i].value){flag = 1;for (int j = 0; j < HFCode[i].codelen; j++){if (len != 0)save = save << 1;save = save | (HFCode[i].code[j] - '0');len++;if (len == 8){writecode.write((char *)&save, sizeof(unsigned char));save = 0;len = 0;}}}}if (flag == 0){cout << *now << "沒有找到該字符屬性" << endl;}read.read((char*)now, sizeof(unsigned char));//*now = read.get();}if (len != 0){writecode.write((char*)&save, sizeof(unsigned char));}read.close();writecode.close(); } void Fano::Decode(string sourcefile, string dstfile) {ifstream read;ofstream write;vector<CountVector> arr;unsigned char now; //讀取的當前字符int last = 0; //最后一次讀取的位數int n; //字符種數read.open(sourcefile, ios::in | ios::binary); //讀取解碼文件write.open(dstfile, ios::out | ios::binary); //打開解碼后的文件read.read((char*)&now, sizeof(now));if (!(now == '>')){cout << "該文件的Huffman編碼格式不正確" << endl << endl;read.close();return;}read.read((char*)&n, sizeof(int));read.read((char*)&last, sizeof(last));for (int i = 0; i < n; i++){CountVector *insert = new CountVector;read.read((char*)&(insert->value), sizeof(unsigned char));read.read((char*)&(insert->frequency), sizeof(int));arr.push_back(*insert);}this->root = new FanoNode;CreateTree(arr, root);GetFanoCode(root, 0);FanoNode *pNow = root;unsigned char *temp = new unsigned char; //每次讀一個字節read.read((char*)temp, sizeof(unsigned char));while (!read.eof()){unsigned char *ifLast = new unsigned char; //用于判斷是否讀到文件末尾read.read((char*)ifLast, sizeof(unsigned char));int i;if (read.eof()){i = last - 1;}else{i = 7;}for (; i >= 0; i--){if ((*temp >> i & 1) == 0) //向右移動7位判斷讀出的是0 還是1 pNow = pNow->Lchild;elsepNow = pNow->Rchild;if (pNow->Lchild == NULL && pNow->Rchild == NULL){write.write((char*)&(pNow->value), sizeof(unsigned char));pNow = root;}}temp = ifLast;}read.close();write.close(); }Fano.cpp
#include "fano.h" #include <string> #include <time.h> int main() {string filepath;cout << "請輸入待壓縮文件的地址" << endl << endl;getline(cin, filepath);clock_t start, end;start = clock();/*Fano myFano;myFano.open(filepath);myFano.count();myFano.root = new Fano::FanoNode;myFano.CreateTree(myFano.charFrequency, myFano.root);myFano.GetFanoCode(myFano.root, 0);myFano.WriteCode(myFano.FanoCodeVec);end = clock();cout << "壓縮文件用時:" << double((end - start) / CLOCKS_PER_SEC) << "/s" << endl;*/Fano myfanoDecode;myfanoDecode.open(filepath);myfanoDecode.Decode(filepath + ".dada", "./Result/ " + filepath);end = clock();cout << "解壓文件用時:" << double((start - end) / CLOCKS_PER_SEC) << "/s" << endl;getchar();}總結
以上是生活随笔為你收集整理的信息论实验-信源编码算法 (Huffman and Shannonn Fano编码C++实现)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机课禁用监视器,win7系统防止别人
- 下一篇: 典型微型计算机控制系统的实例,微型计算机