哈夫曼編碼應該算數據結構“樹”這一章最重要的一個問題了,當時大一下學期學的時候沒弄懂,一年后現在算是明白了。
首先,講講思路。
正好這學期在學算法,這里面就用到了貪心算法,剛好練練手。
整個問題有幾個關鍵點:
1,首先是要思考怎么樣存下從txt中讀取的所有字符中的每種字符出現的次數,首先想到的應該是結構體數組,再仔細想想不對呀,時間復雜度太高了,每次判斷一個字符都對知道它屬於結構體數組中的哪一個,那要是txt中有很多字符,那光這個就得花好多時間。然后想想,如果讀取的字符如果只有那255個ASCII中的字符時可以直接用一個整形數組來表示呀!數組的下標為整數,整數和字符數不正是通過ASCII碼一一對應了嗎。當然這些字符必須全部是那255個中的。所以整形數組的大小也只需255.當然了,如果對C++關聯容器的知識比較熟悉,用關聯容器更方便,畢竟,我們這種方法其實就是在模擬關聯容器。
2,然后我們怎么從剛才得到的各個字符的頻率來整出一顆哈夫曼樹呢?首先,離散數學上講的構建哈夫曼樹應該是比較容易理解的,就是每次選取兩個權值(也就是這里的頻率)最小的兩個節點作為左右孩子(小的在左,大的在右),然后把它們的權值之和作為一個新的待選擇的結點去和剩余結點判斷,自底向上構建,直到剩余權值最大的一個結點,完畢。雖然這樣說着很簡單,但是落實到代碼就值得思考了。我們該怎么樣表示這樣一棵樹呢?習慣性地用指針?認真思考后我們發現,用指針行不通,因為這棵樹是自底向上構建的,我們在構建完成之前是不知道這棵樹的遍歷序列的,也就沒法通過遞歸的形式來創建這棵樹。那么,我們現在應該想到的是用數組,沒錯,用數組可以!顯然是結構體數組,所以得考慮結構體中要有哪些變量,首先要有每個結點表示的字符,對應的權值,還要有左右孩子的下標,雙親的下標。這樣就可以在數組中存下這棵樹了。
3,在得到哈夫曼樹后,該怎么輸出每個字符對應的哈夫曼編碼呢?從每個葉子結點出發,逐步向根結點方向遍歷,每次到達其雙親時,判斷自己是雙親的左孩子還是右孩子,如果是左孩子,在該葉子表示的字符對應的編碼(用string表示)加上0,否則加上1。然后再找到雙親的雙親,。。。直到到達根結點(根結點沒有雙親,對應的雙親下標可以設為0),由於這樣得到的編碼01字符串是反的,所以我們要反一下就得到了正確的編碼。
這里放一個ppt,是我們老師上課講的,便於理解 http://files.cnblogs.com/files/journal-of-xjx/huffman.pptx
注意我是把一個txt文件中的所有字符讀到一個string中去的。自己測試的時候隨便放一個input.txt進去就可以了(注意必須是ASCII小於等於255的字符,超過這個范圍的字符不能用這種方式表示)
代碼如下:
1 #include <iostream> 2 #include <fstream> 3 #include <algorithm> 4 5 using namespace std; 6 7 #define NUM_CHARS 256 //讀取的字符串中最多出現這么多種字符 8 #define MAX_FREQ 10000 //最大頻率必須小於這個數 9 #define MAX_SIZE 512 10 11 //Huffman Tree結點 12 typedef struct HuffNode 13 { 14 char data; 15 unsigned int freq; 16 int parent; 17 int lchild; 18 int rchild; 19 }HuffNode; 20 //編碼結點 21 typedef struct HuffCode 22 { 23 char data;//保存字符 24 string s;//保存字符對應的編碼 25 }HuffCode; 26 27 //給定一個字符串,把字符的出現頻率保存到freqs數組中 28 //注意字符出現的頻率不能超出unsigned int所能表示的范圍 29 int Create_freq_array(unsigned int (&freqs)[NUM_CHARS],string s, int &char_size)//傳入數組的引用, 30 { 31 int i, maxfreq = 0; 32 for(int i=0;i<NUM_CHARS;i++) 33 freqs[i] = 0;//注意傳入的數組的各元素先賦值為0 34 for(auto iter =s.begin(); iter!=s.end(); iter++) 35 { 36 freqs[*iter]++; //*iter為char型,這里轉換成了int型,即以某個字符的ASCII碼作為 37 if(freqs[*iter] > maxfreq)//它在freq數組中的下標,注意這種方式不能表示非ASCII碼字符! 38 maxfreq = freqs[*iter];//每次記得更新maxfreq的值 39 } 40 for(i=0; i<NUM_CHARS; i++)//計算char_size值 41 { 42 if(freqs[i]) 43 { 44 char_size++; 45 } 46 } 47 return 0; 48 } 49 50 //打印字符頻率表 51 int Print_freqs(unsigned int (&freqs)[NUM_CHARS],int n) 52 { 53 int i; 54 char c; 55 for(i = 0; i < NUM_CHARS; i++) 56 { 57 if(freqs[i]) 58 { 59 c = i;//把i以ASCII碼值還原出對應的字符 60 cout << "字符 " << c << " 出現的頻率為:" << freqs[i] << endl; 61 } 62 63 } 64 cout << endl << "以上共出現" << n << "種字符" << endl <<endl; 65 return 0; 66 } 67 68 int Build_Huffman_tree(unsigned int (&freqs)[NUM_CHARS],HuffNode (&Huffman_array)[MAX_SIZE],int n) 69 { //n表示freqs數組中實際包含的字符種類數 70 char c; 71 int k = 0,x1,x2; 72 unsigned int m1, m2; 73 74 for(int i=0;i<NUM_CHARS;i++)//把前n個葉結點的信息輸入Huffman_array數組 75 { 76 if(freqs[i]) 77 { 78 c=i;//還原字符 79 Huffman_array[k].data = c; 80 Huffman_array[k].freq = freqs[i]; 81 Huffman_array[k].parent = 0; 82 Huffman_array[k].lchild = 0; 83 Huffman_array[k].rchild = 0; 84 k++; 85 } 86 } 87 for(int i=n;i<2*n-1;i++)//處理剩下n-1個非葉子結點 88 { 89 Huffman_array[i].data = '#'; 90 Huffman_array[i].freq = 0; 91 Huffman_array[i].parent = 0; 92 Huffman_array[i].lchild = 0; 93 Huffman_array[i].rchild = 0; 94 } 95 // 循環構造 Huffman 樹 96 for(int i=0; i<n-1; i++) 97 { 98 m1=m2=MAX_FREQ; // m1、m2中存放兩個無父結點且結點權值最小的兩個結點 99 x1=x2=0; //x1、x2:構造哈夫曼樹不同過程中兩個最小權值結點在數組中的序號 100 /* 找出所有結點中權值最小、無父結點的兩個結點,並合並之為一顆二叉樹 */ 101 for (int j=0; j<n+i; j++) 102 { 103 if (Huffman_array[j].freq < m1 && Huffman_array[j].parent==0) 104 { //如果當前判斷的結點的權值小於最小的m1,則把它賦給m1,同時 105 m2=m1; //更新m1結點的下標, 保持m1是當前所有判斷過的元素中是最小的 106 x2=x1; //再把m1的信息賦給m2,保持m2是當前所有判斷過的元素中是第二小的 107 m1=Huffman_array[j].freq ; 108 x1=j; 109 } 110 else if (Huffman_array[j].freq < m2 &&Huffman_array[j].parent==0) 111 //如果當前判斷的結點的權值大於等於最小的m1,但是小於m2, 112 { //則只需把它賦給m2,更新m2,保持m2是當前所有判斷過的元素中是第二小的 113 m2=Huffman_array[j].freq ; 114 x2=j; 115 } 116 } 117 /* 設置找到的兩個子結點 x1、x2 的父結點信息 */ 118 Huffman_array[x1].parent = n+i; 119 Huffman_array[x2].parent = n+i; 120 Huffman_array[n+i].freq = Huffman_array[x1].freq + Huffman_array[x2].freq ; 121 Huffman_array[n+i].lchild = x1; 122 Huffman_array[n+i].rchild = x2; 123 } 124 return 0; 125 } 126 //哈夫曼編碼,輸出string中各種字符對應的編碼 127 int Huffman_code(HuffNode(&Huffman_array)[MAX_SIZE],HuffCode (&Huffman_code_array)[NUM_CHARS],int n) 128 { 129 int temp; 130 for(int i = 0;i < n;i++) 131 { 132 temp = i;//當前處理的Huffman_array數組下標 133 Huffman_code_array[i].data = Huffman_array[i].data; 134 while(Huffman_array[temp].parent) 135 { 136 if(Huffman_array[Huffman_array[temp].parent].lchild == temp)//左孩子為0 137 { 138 Huffman_code_array[i].s += '0'; 139 } 140 else//右孩子為1 141 { 142 Huffman_code_array[i].s += '1'; 143 } 144 temp = Huffman_array[temp].parent; 145 } 146 reverse(Huffman_code_array[i].s.begin(), Huffman_code_array[i].s.end()); 147 } //注意翻轉每一個string,這里用到了#include <algorithm> 148 return 0; 149 } 150 151 int Print_huffman_code(HuffCode (&Huffman_code_array)[NUM_CHARS],int n) 152 { 153 for(int i = 0;i < n;i++) 154 { 155 cout << "字符 " << Huffman_code_array[i].data << " 對應的哈夫曼編碼為:" << Huffman_code_array[i].s << endl; 156 } 157 cout << endl; 158 return 0; 159 } 160 161 int main() 162 { 163 ifstream in("input.txt",ios::in);//從input.txt中讀取輸入數據 164 ofstream out("output.txt",ios::out);//向output.txt中寫入數據 165 string s,temp; 166 int char_size = 0;//用以保存string中所包含的字符種類 167 unsigned int freqs[NUM_CHARS]; 168 HuffNode Huffman_array[MAX_SIZE]; 169 HuffCode Huffman_code_array[NUM_CHARS]; 170 while(getline(in,temp))//按行讀取一個txt文件中的各個字符到一個string,每讀完一行加上一個'\n' 171 { 172 s += temp; 173 s += '\n'; 174 } 175 cout << "輸入的字符總數為: " << s.size() << endl << endl << "其中:" << endl;//string中包含的字符數 176 Create_freq_array(freqs,s,char_size); 177 Print_freqs(freqs,char_size); 178 Build_Huffman_tree(freqs,Huffman_array,char_size); 179 Huffman_code(Huffman_array,Huffman_code_array,char_size); 180 Print_huffman_code(Huffman_code_array,char_size); 181 return 0; 182 }
參考了別人的一些代碼。