Huffman编码(Huffman树)
【0】README
0.1) 本文總結于 數據結構與算法分析, 源代碼均為原創, 旨在 理解 “Huffman編碼(Huffman樹)” 的idea 并用源代碼加以實現;
0.2) Huffman樹的構建算法屬于 貪婪算法, 貪婪算法的基礎知識參見: http://blog.csdn.net/pacosonswjtu/article/details/50071159
【1】Huffman 編碼
1.1)貪婪算法的第二個應用: 文件壓縮;
- 1.1.1)標準的 ASCII字符集: 它由大約100個 可打印字符組成,為了把這些字符區分開, 需要 |log100|(不小于等于log100) = 7個 比特;
1.1.2)看個荔枝:(使用一個標準編碼方案)
設一個文件, 它只包含字符 a, e, i, s, t, 加上一些空格和 newline(換行)。 進一步設該文件有10個a、15個e、12個i、3個s、4個t、13個空格以及一個 newline, 如圖10-8所示, 這個文件需要174個bits 來表示,因為有58個字符,每個字符3個bits;
1.2)現實中的事實: 文件可能相當大。 許多大文件是某個程序的輸出數據,而在使用頻率最大和最小之間的字符間通常存在很大的差別;1.2.1)出現的問題:是否有可能提供一種更好的編碼降低總的所需bits數量
2.2)解決方法:一般策略就是讓代碼的長度從字符到字符是編號不等的, 同時保證經常出現的字符其代碼短;(注意, 如果所有的字符都以相同的頻率出現, 那么要節省空間是不可能的)
2.3)代表字母的二進制代碼用二叉樹來表示:
對上圖的分析(Analysis):
- A1)上圖中的樹只在樹葉上有數據。 每個字符通過從根節點開始用0指示 左分支用1指示右分支 而以記錄路徑的方法表示出來。如, s通過從根向左走, 然后向右, 最后再向右而達到, 于是它被編碼為 011。這種數據結構叫做 trie樹;(trie==單詞查找樹)
- A2)如果字符 ci 在深度 di 處并且出現 fi次, 那么該字符編碼的值就等于 ∑di * fi;
- A3)因為 newline(nl)是僅有的一個兒子,我們把它放到它的更高一層的父節點上, 如下圖所示:
- A4)上圖中的樹是滿樹(full tree):所有的節點或者是樹葉,或者有兩個兒子;
1.4)綜上所述: 我們看到,基本的問題在于找到總價值最小的滿二叉樹,其中所有的字符都位于樹葉上, 下圖顯示了簡單字母表的最優樹:
- 1.4.1)那么問題來了:如何構造編碼樹;
- 1.4.2)解決方法(引入哈夫曼編碼): 1952年 Huffman 給出了一個算法, 因此,這種編碼系統通常稱為 哈夫曼編碼(Huffman code);
【2】哈夫曼算法
2.1)算法描述:
算法對一個由樹組成的森林 進行。一棵樹的權等于它的樹葉的頻率的和。任意選取最小權的兩顆樹T1 和 T2, 并任意形成以 T1 和 T2 為子樹的新樹, 將這樣的過程進行 C-1 次。在算法的開始, 存在C 顆單節點樹——每個字符一顆。在算法結束時得到一顆樹, 這顆樹就是最優哈夫曼編碼樹了;
2.2)看個荔枝(構建哈夫曼編碼樹的steps):
2.3)下面,我們驗證哈夫曼算法產生最優代碼的證明思路:
- step1)首先 ,由反證法證明樹是滿的, 因為我們已經看到一顆不滿的樹是改進進成滿樹的;
- step2)其次,我們必須證明兩個頻率最小的字符 α和β必然是兩個最深的節點;
- step3)然后,我們再證明, 在相同深度上任意兩個節點處的字符可以交換而不影響最優性;這說明, 總可以找到一顆樹, 它含有兩個最不經常出現的符號作為兄弟;
2.4)該算法是貪婪算法的原因在于: 在每一階段我們都進行一次合并而沒有進行全局的考慮, 我們只是選擇兩顆最小的樹;
2.5) 如何實現?
- method1)如果我們依權排序將這些樹保存在一個優先隊列中, 那么, 由于對元素個數不超過C的優先隊列將進行一次 buildHeap , 2C -2 次 deleteMin 和 C-2 次insert, 故運行時間為 O(ClogC)。
- method2)使用一個鏈表簡單實現該隊列將給出一個O(C^2)算法;
- Conclusion) 優先隊列實現方法的選擇取決于C有多大: 在ASCII 字符集下,C是足夠小的, 這使得二次運行時間是可以接收的。 在這樣的應用中, 實際上幾乎所有的運行時間都將花費在讀入輸入文件和寫出壓縮文件所需要的磁盤I/O 操作上;
2.6)有兩個細節要考慮(details)
- d1)第一個問題是: 首先, 在壓縮文件的開頭必須傳送編碼信息,因為否則將不可能譯碼;對于一些小文件,傳送編碼信息表的代價將超過壓縮帶來的任何可能的節省, 最后的結果很可能是文件擴大。 當然,這可以檢測到且原文件可以原樣保留; 而對于大型文件, 信息表的大小是無關緊要的;
- d2)第二個問題是: 該算法是一個兩趟掃描算法。 第一遍搜集頻率數據, 第二遍進行編碼。顯然, 對于處理大型文件的程序來說,這個性質不是我們所需要的;
【3】source code + printing results
3.1)download source code: https://github.com/pacosonTang/dataStructure-algorithmAnalysis/tree/master/chapter10/p266_huffman
3.2)source code at a glance:(for complete code , please click the given link above)
1st func: building huffman tree
// building huffman tree void buildHuffman() { ElementTypePtr temp; char* codes;int off;off = 0;codes = buildCharArray(off+1);temp = buildElement(); initElement(temp);while(!isEmpty(bh)){ insertHeap(*temp, bh); initElement(temp);} temp->code = '\0';printf("\n=== the huffman tree we built just now is follows: ===\n");printHuffmanTree(temp, 1);printf("\n=== the huffman codes of left subtree ===\n");printHuffmanCode(temp->left, 1, off, codes);printf("\n=== the huffman codes of right subtree ===\n");printHuffmanCode(temp->right, 1, off, codes); }2nd func: print huffman tree node
void copyCharArray(char *a, char *b, int size) {int i;for(i=0; i<=size; i++)a[i] = b[i]; }void printHuffmanTree(ElementTypePtr root, int depth) { int i;if(root) { for(i = 0; i < depth; i++)printf(" "); if(root->left!=NULL) printf("%d\n", root->key); elseprintf("%d->%c\n", root->key, root->flag);printHuffmanTree(root->left,depth+1); printHuffmanTree(root->right, depth+1); // Attention: there's difference between traversing binary tree and common tree }else {for(i = 0; i < depth; i++)printf(" "); printf("NULL\n");} }3rd func: print huffman code of every node
void printHuffmanCode(ElementTypePtr root, int depth, int off, char *codes) { int i; char *innerCode = buildCharArray(off+2); copyCharArray(innerCode, codes, off);if(root) { innerCode[off] = root->code;innerCode[++off] = '\0'; for(i = 0; i < depth; i++)printf(" "); if(root->left!=NULL) printf("%d\n", root->key); elseprintf("%d->%c->%s\n", root->key, root->flag, innerCode);printHuffmanCode(root->left,depth+1, off, innerCode); printHuffmanCode(root->right, depth+1, off, innerCode); // Attention: there's difference between traversing binary tree and common tree }else {for(i = 0; i < depth; i++)printf(" "); printf("NULL\n");} }3.3)printing results:
總結
以上是生活随笔為你收集整理的Huffman编码(Huffman树)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 怎么在万网祖册免费的二级域名(怎么在万网
- 下一篇: 近似装箱问题(两种脱机算法实现)