霍夫曼編碼

霍夫曼編碼（Huffman Coding），又譯為哈夫曼編碼、赫夫曼編碼，是一種用於無損資料壓縮的熵編碼（權編碼）演演算法。由美國電腦科學家大衛・霍夫曼於 1952 年發明。

簡介

在計算機資訊處理中，霍夫曼編碼使用變長編碼表對源符號（如檔案中的一個字母）進行編碼，其中變長編碼表是透過一種評估來源符號出現機率的方法得到的，出現機率高的字母使用較短的編碼，反之出現機率低的則使用較長的編碼，這便使編碼之後的字串的平均長度、期望值降低，從而達到無失真壓縮資料的目的。

例如，在英文中，e 的出現機率最高，而 z 的出現機率則最低。當利用霍夫曼編碼對一篇英文文章進行壓縮時，e 極有可能用一個位元來表示，而 z 則可能花去 25 個位元（不是 26）。用普通的表示方法時，每個英文字母均佔用一個位元組，即 8 個位元。二者相比，e 使用了一般編碼的 1/8 的長度，z 則使用了 3 倍多。倘若我們能實現對於英文中各個字母出現機率的較準確的估算，就可以大幅度提高無失真壓縮的比例。

霍夫曼樹又稱最優二叉樹，是一種帶權路徑長度最短的二叉樹。所謂樹的帶權路徑長度，就是樹中所有的葉結點的權值乘上其到根結點的路徑長度（若根結點為 0 層，葉結點到根結點的路徑長度為葉結點的層數）。樹的路徑長度是從樹根到每一結點的路徑長度之和，記為 WPL=（W1L1+W2L2+W3L3+...+WnLn），N 個權值 Wi（i=1,2,...n）構成一棵有 N 個葉結點的二叉樹，相應的葉結點的路徑長度為 Li（i=1,2,...n）。可以證明霍夫曼樹的 WPL 是最小的。

歷史

1951 年，霍夫曼在麻省理工學院攻讀博士學位，他和修讀資訊理論課程的同學得選擇是完成學期報告還是期末考試。導師羅伯特・法諾出的學期報告題目是：尋找最有效的二進位制編碼。由於無法證明哪個已有編碼是最有效的，霍夫曼放棄對已有編碼的研究，轉向新的探索，最終發現了基於有序頻率二叉樹編碼的想法，並很快證明瞭這個方法是最有效的。霍夫曼使用自底向上的方法構建二叉樹，避免了次優演算法夏農 - 範諾編碼的最大弊端──自頂向下構建樹。

1952 年，於論文《一種構建極小多餘編碼的方法》（A Method for the Construction of Minimum-Redundancy Codes）中發表了這個編碼方法。

問題定義與解法

3.1 廣義

給定

一組符號（Symbol）和其對應的權重值（weight），其權重通常表示成機率（%）。

欲知

一組二元的前置碼，其二元碼的長度為最短。

3.2 狹義

輸入

符號集合，其 S 集合的大小為。

權重集合，其 W 集合不為負數且。

輸出

一組編碼，其 C 集合是一組二進位制編碼且為相對應的編碼，。

目標

設為的加權的路徑長，對所有編碼，則

3.3 範例

霍夫曼樹常處理符號編寫工作。根據整組資料中符號出現的頻率高低，決定如何給符號編碼。如果符號出現的頻率越高，則給符號的碼越短，相反符號的號碼越長。假設我們要給一個英文單字 "F O R G E T" 進行霍夫曼編碼，而每個英文字母出現的頻率分別列在 Fig.1。

3.3.1 演算過程

（一）進行霍夫曼編碼前，我們先建立一個霍夫曼樹。

⒈將每個英文字母依照出現頻率由小排到大，最小在左，如 Fig.1。

⒉每個字母都代表一個終端節點（葉節點），比較 F.O.R.G.E.T 六個字母中每個字母的出現頻率，將最小的兩個字母頻率相加合成一個新的節點。如 Fig.2 所示，發現 F 與 O 的頻率最小，故相加 2+3=5。

⒊比較 5.R.G.E.T，發現 R 與 G 的頻率最小，故相加 4+4=8。

⒋比較 5.8.E.T，發現 5 與 E 的頻率最小，故相加 5+5=10。

⒌比較 8.10.T，發現 8 與 T 的頻率最小，故相加 8+7=15。

⒍最後剩 10.15，沒有可以比較的物件，相加 10+15=25。

最後產生的樹狀圖就是霍夫曼樹，參考 Fig.2。

（二）進行編碼

給霍夫曼樹的所有左鏈結 '0' 與右鏈結 '1'。

從樹根至樹葉依序記錄所有字母的編碼，如 Fig.3。

實現方法

4.1 資料壓縮

實現霍夫曼編碼的方式主要是建立一個二元樹和其節點。這些樹的節點可以儲存在陣列裡，陣列的大小為符號（symbols）數的大小 n，而節點分別是終端節點（葉節點）與非終端節點（內部節點）。

一開始，所有的節點都是終端節點，節點內有三個欄位：

符號（Symbol）

權重（Weight、Probabilities、Frequency）

指向父節點的鏈結（Link to its parent node）

而非終端節點內有四個欄位：

權重（Weight、Probabilities、Frequency）

指向兩個子節點的 鏈結（Links to two child node）

指向父節點的鏈結（Link to its parent node）

基本上，我們用 '0' 與 '1' 分別代表指向左子節點與右子節點，最後為完成的二元樹共有 n 個終端節點與 n-1 個非終端節點，去除了不必要的符號並產生最佳的編碼長度。

過程中，每個終端節點都包含著一個權重（Weight、Probabilities、Frequency），兩兩終端節點結合會產生一個新節點，新節點的權重是由兩個權重最小的終端節點權重之總和，並持續進行此過程直到只剩下一個節點為止。

實現霍夫曼樹的方式有很多種，可以使用優先佇列（Priority Queue）簡單達成這個過程，給與權重較低的符號較高的優先順序（Priority），演演算法如下：

⒈把 n 個終端節點加入優先佇列，則 n 個節點都有一個優先權 Pi，1 ≤ i ≤ n

⒉如果佇列內的節點數 > 1，則：

⑴從佇列中移除兩個最小的 Pi 節點，即連續做兩次 remove（min（Pi）, Priority_Queue)

⑵產生一個新節點，此節點為（1）之移除節點之父節點，而此節點的權重值為（1）兩節點之權重和

⑶把（2）產生之節點加入優先佇列中

⒊最後在優先佇列裡的點為樹的根節點（root）

而此演演算法的時間複雜度（Time Complexity）為 O (n log n)；因為有 n 個終端節點，所以樹總共有 2n-1 個節點，使用優先佇列每個迴圈須 O (log n)。

此外，有一個更快的方式使時間複雜度降至線性時間（Linear Time）O (n)，就是使用兩個佇列（Queue）創件霍夫曼樹。第一個佇列用來儲存 n 個符號（即 n 個終端節點）的權重，第二個佇列用來儲存兩兩權重的合（即非終端節點）。此法可保證最小值永遠都是兩個佇列的前端（Front）權重之一，且方法如下：

⒈把 n 個終端節點加入第一個佇列（依照權重大小排列，最小在前端）

⒉如果佇列內的節點數 > 1，則：

⑴從佇列前端移除兩個最低權重的節點

⑵將（1）中移除的兩個節點權重相加合成一個新節點

⑶加入第二個佇列

⒊最後在第一個佇列的節點為根節點

雖然使用此方法比使用優先佇列的時間複雜度還低，但是注意此法的第 1 項，節點必須依照權重大小加入佇列中，如果節點加入順序不按大小，則需要經過排序，則至少花了 O (n log n) 的時間複雜度計算。

但是在不同的狀況考量下，時間複雜度並非是最重要的，如果我們今天考慮英文字母的出現頻率，變數 n 就是英文字母的 26 個字母，則使用哪一種演演算法時間複雜度都不會影響很大，因為 n 不是一筆龐大的數字。

4.2 資料解壓縮

簡單來說，霍夫曼碼樹的解壓縮就是將得到的前置碼（Prefix Huffman code）轉換回符號，通常藉由樹的追蹤（Traversal），將接收到的位元串（Bits stream）一步一步還原。但是要追蹤樹之前，必須要先重建霍夫曼樹；某些情況下，如果每個符號的權重可以被事先預測，那麼霍夫曼樹就可以預先重建，並且儲存並重複使用，否則，傳送端必須預先傳送霍夫曼樹的相關資訊給接收端。

最簡單的方式，就是預先統計各符號的權重並加入至壓縮之位元串，但是此法的運算量花費相當大，並不適合實際的應用。若是使用 Canonical encoding，則可精準得知樹重建的資料量只佔 B・2^B 位元（其中 B 為每個符號的位元數（bits））。如果簡單將接收到的位元串一個位元一個位元的重建，例如：'0' 表示父節點，'1' 表示終端節點，若每次讀取到 1 時，下 8 個位元則會被解讀是終端節點（假設資料為 8-bit 字母），則霍夫曼樹則可被重建，以此方法，資料量的大小可能為 2~320 位元組不等。雖然還有很多方法可以重建霍夫曼樹，但因為壓縮的資料串包含 "trailing bits"，所以還原時一定要考慮何時停止，不要還原到錯誤的值，如在資料壓縮時時加上每筆資料的長度等。

基本性質

考量到不同的應用領域以及該領域的平均性質，將會使用不同的通用機率，又或者，這個機率是可以從被壓縮文字中的實際頻率所取得。

5.1 最佳化

原始的霍夫曼演演算法是一個符號與符號間已知輸入機率分佈的最佳編碼方式，也就是說將不相關的符號個別編碼為如此的資料串流。然而，當符號間的限制被捨棄或是質量機率函式是未知的時候，如此方式則並非最佳化。此外，當這些符號之間不是互相獨立，且分佈不相同的時候，單一一個符號可能不足以實現最佳化。在這種情況之下，算術編碼可能比起霍夫曼編碼會有更佳的壓縮能力。

雖然上述兩種方法都可以組合任意數量的符號以實現更高效的編碼效果，並且通常適應於實際的輸入統計層面，但儘管最簡單的版本比霍夫曼編碼更慢且更複雜，算術編碼不會顯著增加其計算或演算法複雜度。當輸入機率不是精確已知或在流內顯著變化時，這種靈活性特別有用。然而，霍夫曼編碼通常更快，並且算術編碼在歷史上是專利問題的一些主題。因此，許多技術歷來避免使用有利於霍夫曼和其他字首編碼技術的算術編碼。截至 2010 年中期，隨著早期專利的到期，這種替代霍夫曼編碼的最常用技術已經進入公有領域。

對於具有均勻機率分佈的一組符號，以及作為 2 的冪之成員，霍夫曼編碼等同於簡單的二進位制編碼，例如 ASCII 編碼。這反映瞭如此的事實：無論壓縮方法是什麼，這種輸入都不可能進行壓縮，或只是說對資料無所作為，比起壓縮才是最佳選擇。

在任何情況下，霍夫曼編碼在所有方法中是最佳的方式，其中每個輸入符號是具有二元機率的已知獨立且相同分佈的隨機變數。字首碼，特別是霍夫曼編碼，往往在小字母表上產生較差的效率，其中機率通常落在這些最佳（二元）點之間。當最可能符號的機率遠超過 0.5 時，可能發生霍夫曼編碼的最壞情況，使低效率的上限無限制。

在使用霍夫曼編碼的同時，有兩種相關的方法可以解決這種特定的低效問題。將固定數量的符號組合在一起（阻塞）通常會增加（並且永不減少）壓縮。隨著塊的大小接近無窮大，霍夫曼編碼理論上接近熵限制，即最佳壓縮。然而，阻塞任意大的符號組是不切實際的，因為霍夫曼程式碼的複雜性在要編碼的可能性的數量上是線性的，這是在塊的大小中呈指數的數字。這限制了在實踐中完成的阻塞量。

廣泛使用的實際替代方案是行程編碼。該技術在熵編碼之前增加一步，特別是對重複符號進行執行次數的計數，然後對其進行編碼。對於伯努力（Bernoulli）過程的簡單情況，哥倫（Golomb）編碼在編碼遊程長度的字首碼中是最佳的，這是透過霍夫曼編碼技術證明的事實。使用改進的霍夫曼編碼的傳真機採用類似的方法。但是，遊程編碼並不像其他壓縮技術那樣適應許多輸入型別。

變化

霍夫曼編碼有衍生出許多的許多變化，其中一些使用類似霍夫曼的演算法，而其他一些演算法找到最佳字首碼（例如，對輸出施加不同的限制）。注意，在後一種情況下，該方法不需要像霍夫曼那樣，實際上甚至不需要到多項式複雜度。

6.1 多元樹霍夫曼編碼

多元樹霍夫曼編碼演演算法使用字母集 {0, 1, ... , n − 1} ，來構建一棵 n 元樹。霍夫曼在他的原始論文中考慮了這種方法。這與二進位制（n=2）演算法僅有的差別，就是它每次把 n 個最低權的符號合併，而不僅是兩個最低權的符號。倘若 n ≥ 2, 則並非所有源字集都可以正確地形成用於霍夫曼編碼的多元樹。在這些情況下，必須新增額外的零機率佔位符。這是因為該樹必須為滿的 n 叉樹，所以每次合併會令符號數恰好減少 (n -1), 故這樣的 n 叉樹存在當且僅當源字的數量模 (n -1) 餘 1. 對於二進位制編碼，任何大小的集合都可以形成這樣的二叉樹，因為 n -1 = 1.

6.2 自適應霍夫曼編碼

自適應霍夫曼編碼的變化，涉及基於源符號序列中的最近實際頻率動態地計算機率，以及改變編碼樹結構以匹配更新的機率估計。它在實踐中很少使用，因為更新樹的成本使其比最佳化的自適應算術編碼慢，後者更靈活並且具有更好的壓縮。

6.3 霍夫曼模板演演算法

在霍夫曼編碼的實現中，通常會使用權重表示數值機率，但是上面給出的演算法不需要這樣；它只需要權重形成一個完全有序的可交換麼半群，這意味著一種訂購權重和新增權重的方法。霍夫曼模板演算法使人們能夠使用任何型別的權重（成本，頻率，權重對，非數字權重）和許多組合方法之一（不僅僅是加法），這個問題首先應用於電路設計。

6.4 長度限制霍夫曼編碼 / 最小變異霍夫曼編碼

6.4.1 長度限制霍夫曼編碼

長度受限的霍夫曼編碼是一種變體，其目標仍然是實現最小加權路徑長度，但是存在另外的限制，即每個碼字的長度必須小於給定常數。包合併演算法透過一種與霍夫曼演演算法非常相似的簡單貪婪方法解決了這個問題。

6.5 不相等成本霍夫曼編碼

在標準的霍夫曼編碼問題中，假設集合中構成碼字的每個符號具有相同的傳輸成本：長度為 N 位的碼字總是具有 N 的成本，無論多少這些數字是 0，有多少是 1，等等。在這個假設下工作時，最小化訊息的總成本和最小化數字總數是相同的。

具有不等字母成本的霍夫曼編碼是沒有這種假設的概括：由於傳輸介質的特性，編碼字母表的字母可能具有不均勻的長度。一個例子是摩爾斯電碼的編碼字母表，其中 ' 破折號 ' 比' 點 ' 需要更長的傳送時間，因此傳輸時間中破折號的成本更高。目標仍然是最小化加權平均碼字長度，但僅僅最小化訊息使用的符號數量已經不夠了。沒有演算法以與傳統霍夫曼編碼相同的方式或相同的效率來解決這個問題，儘管它已經由卡普（Karp）解決，其解決方案已經針對哥林（Golin）的整數成本的情況進行了改進。

6.6 最佳字母二元樹

在標準霍夫曼編碼問題中，假設任何碼字可以對應於任何輸入符號。在字母表中，輸入和輸出的字母順序必須相同。

6.7 規範霍夫曼編碼

如果對應於按字母順序排列的輸入的權重是按數字順序排列的，則霍夫曼程式碼具有與最佳字母程式碼相同的長度，這可以從計算這些長度中找到，從而不需要使用胡 - 塔克（Hu-Tucker）編碼。由數字（重新）有序輸入產生的程式碼有時被稱為規範霍夫曼程式碼，並且由於易於編碼 / 解碼，通常是實踐中使用的程式碼。用於找到該程式碼的技術有時被稱為霍夫曼 - 夏農 - 法諾編碼，因為它像霍夫曼編碼一樣是最優的，但是在重量機率上是字母的，例如夏農 - 法諾編碼。

資料長度

設符號集合，為發生的機率

，為編碼的長度

熵（Entropy）: 亂度

ex:

第三與第四個範例，同樣是五種組合，機率分佈越集中，則亂度越少

霍夫曼碼平均長度

霍夫曼碼的長度

Shannon 編碼定理： ≤ ≤ ，若使用進位的編碼

霍夫曼碼的平均編碼長度：設，為資料長度

 ≤ ≤

範例

當有 100 個位子，有白色佔 60%、咖啡色佔 20%，藍色和紅色各佔 10%，則該如何分配才能最佳化此座位？(a) direct:

假設結果為：白色為 00、咖啡色 01，藍色 10 和紅色 11 個 bits

則結果為：100*2 = 200bits

(b) huffman code: (must be satisfy the following conditions，if not change the node)

(1) 所有碼皆在 Coding Tree 的端點，再下去沒有分枝 (滿足一致解碼跟瞬間解碼)

(2) 機率越大，code length 越短；機率越小，code length 越長

(3) 假設是第 L 層的 node，是第 L+1 層的 node

則必須滿足

假設結果為：白色佔 0、咖啡色 10，藍色 110 和紅色 111 個 bits

則結果為：601+202+20*3=160bits

相互比較兩個結果 huffman code 整整少了 40bits 的儲存空間。

示範程式

// 以下為 C++ 程式碼，在 G++ 下編譯透過

// 僅用於示範如何根據權值建構霍夫曼樹，

// 沒有經過效能上的最佳化及加上完善的異常處理。

include

using namespace std;

const int size = 10;

struct node { // 霍夫曼樹節點結構

unsigned key; // 儲存權值

node *lchild; // 左孩子指標

node *rchild; // 右孩子指標

};

deque forest;

deque code; // 此處也可使用 bitset

node *ptr;

int frequencysize = {0};

void printCode (deque ptr); // 用於輸出霍夫曼編碼

bool compare (node a, node b) {return a->key key;

}

int main (int argc, char *argv) {for (int i = 0; i> frequencyi; // 輸入 10 個權值

ptr = new node;

ptr->key = frequencyi;

ptr->lchild = NULL;

ptr->rchild = NULL;

forest.push_back (ptr);

} // 形成森林，森林中的每一棵樹都是一個節點

// 從森林構建霍夫曼樹

for (int i = 0; i key = forest0->key + forest1->key;

ptr->lchild = forest0;

ptr->rchild = forest1;

forest.pop_front ;

forest.pop_front ;

forest.push_back (ptr);

}

ptr = forest.front ; //ptr 是一個指向根的指標

system ("PAUSE");

return EXIT_SUCCESS;

}

void printCode (deque ptr) {

//deque

for (int i = 0; i

霍夫曼編碼 C 程式碼

huffman.h 檔案

ifndef HUFFMAN_H
define HUFFMAN_H
include
define START printf ("=====start=====\n")
define END printf ("=====end=====\n")
define toByte (n) ((n) / 8 + ((n) % 8 > 0))

typedef struct HuffListNode HuffListNode,*hufflistnodep;

typedef struct HuffNode HuffNode,*huffnodep;

typedef struct HuffTree HuffTree,*hufftreep;

typedef struct HuffCode HuffCode,*huffcodep;

typedef struct HuffList HuffList,*hufflistp;

typedef struct HuffResult HuffResult,*huffresultp;

typedef struct HuffCode HuffBuf,*huffbufp; // 快取型別

struct HuffListNode {

huffnodep node; //huffman 節點

hufflistnodep next; // 後繼節點

}; //huffman 頻率節點

struct HuffList {

hufflistnodep head; // 頭結點

int keys256; // 鍵值字典

int size; // 連結串列長度

};

struct HuffNode {

int key; // 鍵

int weight; // 權重

huffnodep left; // 左節點

huffnodep right; // 右節點

}; //huffman 節點

struct HuffCode {

char* code; //huffman code

int size; //huffman code size

};

struct HuffTree {

huffnodep root; // 根

huffcodep codes256; //key 對應的程式碼

int size; // 大小

}; //huffman 樹

struct HuffResult {

char* code; // 生成的程式碼

hufftreep tree; // 對應的霍夫曼樹

};

ifndef BOOLEAN
define BOOLEAN

typedef enum {

FALSE = 0,

TRUE = 1,

} Boolean;

endif

huffnodep huffnode (int key,int weight); // 初始化 huffman 節點

hufflistp hufflist ; // 初始化 hufflist

Boolean insertHuffNode (hufflistp list,huffnodep node); // 向指定的節點連結串列新增一個節點

huffnodep shiftHuffNode (hufflistp list); // 刪除第一個節點

hufftreep hufftree (hufflistp list); // 構建一棵 huffman tree

huffbufp getFileBuf (const char* filename); // 獲取檔案的 buf

hufftreep genhuffcodes (hufftreep tree,huffnodep node,char codes,int idx); // 獲取當前節點之下的節點的 huffman 編碼

hufflistp getHuffListByFile (const char* filename); // 根據檔案建立 huffman 連結串列

hufflistp getHuffListByBuf (huffbufp buf); // 根據檔案 buf 建立 huffman 連結串列

huffcodep getHuffCode (hufftreep tree,int key); // 獲取指定鍵值的 huffcode

huffresultp getHuffCodesByFile (const char* filename); // 獲取檔案的 huffman code

huffresultp getHuffCodesByBuf (huffbufp buf); // 透過 buf 獲取 codes

huffbufp getOriginBuf (huffresultp result); // 從 result 中解析出原始的字串

huffbufp str2bin (char* str); // 二進位制字串轉二進位制陣列

int putOriginToFile (huffresultp result,const char* filename); // 將 result 儲存到 filename 中

char* bin2str (huffbufp buf); // 二進位制陣列轉二進位制字串

huffbufp readHuffFile (const char* filename); // 解析 huff 檔案

endif

huffman.c 檔案:

include "huffman.h"

huffnodep huffnode (int key,int weight){huffnodep ret = (huffnodep) malloc (sizeof (HuffNode));

ret->key = key;

ret->weight = weight;

ret->left = ret->right = NULL;

return ret;

}

hufflistp hufflist {hufflistp ret = (hufflistp) malloc (sizeof (HuffList));

ret->head = NULL;

memset (ret->keys,0,sizeof (ret->keys0)*256);

ret->size = 0;

return ret;

}

Boolean insertHuffNode (hufflistp list,huffnodep node){

if (list == NULL

node == NULL

node->weight head;

hufflistnodep* rootp = &(list->head);

hufflistnodep* last = NULL; // 當前指標的前驅指標

hufflistnodep tmp = (hufflistnodep) malloc (sizeof (HuffListNode));

tmp->node = node;

tmp->next = NULL;

if (node->key >= 0 && node->key keysnode->key = node->weight; // 新增 key 到 keys 字典

}

list->size++;

for (;cur != NULL && cur->node->weight weight; cur = cur->next){

last = rootp;

rootp = &(cur->next);

}

tmp->next = cur;

if (last == NULL){ // 第一個元素

list->head = tmp;

} else { // 向當前節點前面插入 tmp 節點

(*last)->next = tmp;

}

return TRUE;

}

huffnodep shiftHuffNode (hufflistp list){

if (list == NULL

list->head == NULL) return NULL;

huffnodep ret = list->head->node;

hufflistnodep next = list->head->next;

free (list->head);

list->head = next;

list->size--;

return ret;

}

// 透過 huffman list 構建

hufftreep hufftree (hufflistp list){hufftreep tree = (hufftreep) malloc (sizeof (HuffTree));

tree->root = NULL;

tree->size = 0;

memset (tree->codes,0,sizeof (tree->codes));

huffnodep a = NULL;

huffnodep b = NULL;

huffnodep c = NULL;

tree->size = 2 * list->size - 1;

while (list->size > 1){ //hufflistp 長度大於 1

a = shiftHuffNode (list);

b = shiftHuffNode (list);

c = huffnode (-256,a->weight+b->weight); // 新的節點

c->left = a;

c->right = b;

insertHuffNode (list,c); // 將 c 壓回 list

}

tree->root = c;

// 生成所有 key 的 huffman 編碼

char codes8092; //huffman 編輯路徑

return genhuffcodes (tree,tree->root,codes,0);

}

// 獲取檔案內容的 BUF

huffbufp getFileBuf (const char filename){FILE fp = fopen (filename,"r");

if (fp == NULL) return NULL;

fseek (fp,0L,SEEK_END);

int len = ftell (fp);

rewind (fp); // 重設

huffbufp ret = (huffbufp) malloc (sizeof (HuffBuf));

ret->code = (char*) malloc (len+1);

ret->size = len;

fread (ret->code,1,len,fp);

fclose (fp);

return ret;

}

hufftreep genhuffcodes (hufftreep tree,huffnodep node,char codes,int idx){

if (tree == NULL

node == NULL){ // 到達底部

return NULL;

}

if (node->left == NULL && node->right == NULL){ // 葉子節點

int key = node->key;

huffcodep code = (huffcodep) malloc (sizeof (HuffCode));

code->code = (char*) malloc (idx+1);

code->size = idx;

memcpy (code->code,codes,code->size);

code->codecode->size = '\0';

tree->codeskey = code;

}{

codesidx = '1'; // 右

genhuffcodes (tree,node->right,codes,idx+1);

codesidx = '0'; // 左

genhuffcodes (tree,node->left,codes,idx+1);

}

return tree;

}

// 透過檔案生成 huffman list

hufflistp getHuffListByFile (const char* filename){huffbufp buf = getFileBuf (filename);

if (buf == NULL) return NULL;

hufflistp list = getHuffListByBuf (buf);

free (buf->code);

buf->code = NULL;

free (buf);

buf = NULL;

return list;

}

hufflistp getHuffListByBuf (huffbufp buf){

if (buf == NULL

buf->code == NULL) return NULL;

char* code = buf->code;

hufflistp list = hufflist ;

for (int i = 0; codei != '\0'; i++){

unsigned char ch = codei;

list->keysch++;

}

for (int i = 0; i keysi> 0){ // 插入存在的字元

insertHuffNode (list,huffnode (i,list->keysi));

}

}

return list;

}

huffcodep getHuffCode (hufftreep tree,int key){if (key = 0 && tree->codeskey > 0){return tree->codeskey;

}

return NULL;

}

huffresultp getHuffCodesByFile (const char* filename){huffbufp buf = getFileBuf (filename); // 檔案快取

return getHuffCodesByBuf (buf);

}

huffresultp getHuffCodesByBuf (huffbufp buf){huffresultp result = (huffresultp) malloc (sizeof (HuffResult));

result->code = NULL;

if (buf == NULL) return NULL;

hufflistp list = getHuffListByBuf (buf); //huffman list

result->tree = hufftree (list);

int buf_len = buf->size;

int len = 0;

for (int i = 0; buf->codei != '\0'; i++){int key = (unsigned char) buf->codei;

huffcodep code = getHuffCode (result->tree,key);

if (code == NULL){printf ("LLL:% c {% d}\n",key,key);

return NULL;

}

len+=code->size;

}

result->code = (char*) malloc (len+1);

result->code0 = '\0';

for (int i = 0; buf->codei != '\0'; i++){unsigned char key = buf->codei;

huffcodep code = getHuffCode (result->tree,key);

strncat (result->code,code->code,code->size);

}

return result;

}

huffbufp getOriginBuf (huffresultp result){

if (result == NULL

result->code == NULL

result->tree == NULL) return NULL;

hufftreep tree = result->tree;

char* code = result->code;

int len = 0;

for (int i = 0; codei != '\0';){huffnodep root = tree->root; // 根節點

while (root->left != NULL && root->right != NULL && codei != '\0'){ // 雙子節點存在

root = (codei == '0' ? root->left : root->right);

i++;

}

if ((root->left != NULL

root->right != NULL) && codei == '\0'){ // 錯誤

return NULL;

}

len++;

//printf ("解析:% c {% s}\n",root->key,tree->codesroot->key->code);

}

huffbufp ret = (huffbufp) malloc (sizeof (HuffBuf));

ret->code = (char*) malloc (len+1);

ret->code0 = '\0';

ret->size = len;

int idx = 0;

for (int i = 0; codei != '\0';){huffnodep root = tree->root; // 根節點

while (root->left != NULL && root->right != NULL && codei != '\0'){ // 雙子節點存在

root = (codei == '0' ? root->left : root->right);

i++;

}

ret->codeidx++ = root->key;

}

ret->codeidx = '\0';

return ret;

}

int putOriginToFile (huffresultp result,const char* filename){if (result == NULL) return 0;

//printf ("res1% d:% s\n",(int) strlen (result->code),result->code);

//huffbufp b = str2bin (result->code);

//printf ("% d\n",b->size);

//printf ("res2:% s\n",bin2str (b));

//return 0;

huffbufp buf = str2bin (result->code); //huffman code 轉成 buf

int i = 0;

int len = 0;

for (i = 0; i tree->codesi > 0){ //

len+= 5+result->tree->codesi->size; //key1:len4:size

}

}

huffbufp keys = (huffbufp) malloc (sizeof (HuffBuf));

keys->code = (char*) malloc (len);

keys->size = 0;

// 獲取 keys

int idx = 0;

for (i = 0; i tree->codesi > 0){ //

keys->codeidx++ = i; //key

int len = result->tree->codesi->size;

memcpy (keys->code+idx,&len,4); //key size

//printf ("% c% d:% d {% s}\n",i,i,len,result->tree->codesi->code);

idx+=4;

huffbufp tmp = str2bin (result->tree->codesi->code);

//printf ("% d,% d\n",tmp->code0,tmp->size);

int tsize = toByte (tmp->size);

memcpy (keys->code+idx,tmp->code,tsize);

idx+=tsize;

}

}

keys->size = idx; // 諸多鍵的總空間

// 寫出標準檔案

//HUF\n

//size: 4b

//keys

//size: 4b

//codes

FILE* fp = fopen (filename,"w");

if (fp == NULL) return -1;

fwrite ("HUF\n",1,4,fp);

fwrite (&idx,1,4,fp); //size

fwrite (keys->code,1,keys->size,fp); // 寫入 code

fwrite (&(buf->size),1,4,fp); //size

fwrite (buf->code,1,toByte (buf->size),fp);

fclose (fp);

return 4+4+keys->size+4+buf->size;

}

huffbufp str2bin (char* str){ // 二進位制字串轉二進位制陣列

//printf ("bin:% s\n",str);

if (str == NULL) return NULL;

huffbufp buf = (huffbufp) malloc (sizeof (HuffBuf));

int l = strlen (str);

int size = (l / 8) + (l % 8> 0);

buf->code = (char*) malloc (l);

memset (buf->code,0,l);

for (int i = 0; i codeidx |= (stri == '0' ? 0:1) size = l;

return buf;

}

char bin2str (huffbufp buf){char ret = (char*) malloc (buf->size+1);

for (int i = 0; i size; i++){

int idx = i / 8;

int offset = i % 8;

reti = (buf->codeidx & (0x01 size = '\0';

return ret;

}

huffbufp readHuffFile (const char* filename){huffbufp buf = getFileBuf (filename);

if (buf == NULL) return NULL;

if (memcmp (buf->code,"HUF\n",4) != 0) return NULL; // 檔案不以 BUF\n 開頭

huffresultp result = (huffresultp) malloc (sizeof (HuffResult));

//BUF\n

//key size

int key_size = (int)(buf->code+4);

int base = 8; // 偏移量

hufftreep tree = (hufftreep) malloc (sizeof (HuffTree));

tree->root = NULL;

tree->size = 0;

huffcodep* codes = tree->codes; //key 對應程式碼

memset (codes,0,sizeof (huffcodep)*256);

int oft = 0;

for (;oft codeoffset;

//printf ("% d% c\n",key,key);

int size = (int)(buf->code+offset+1); // 長度

int byte = toByte (size);

huffbufp htmp = (huffbufp) malloc (sizeof (HuffBuf));

// 鍵對應程式碼

htmp->code = buf->code+offset+5; // 快取程式碼

htmp->size = size; // 快取大小

//printf ("% c% d\n",key,key);

huffcodep tmp = (huffcodep) malloc (sizeof (HuffCode));

tmp->size = size; //key 的大小

tmp->code = bin2str (htmp);

tree->codeskey = tmp;

tree->size++; // 樹的大小增加

huffnodep root = tree->root;

if (root == NULL){tree->root = huffnode (-256,0);

root = tree->root;

}

for (int i = 0; i size; i++){char ch = tmp->codei;

huffnodep node = NULL;

if (ch == '0'){node = root->left;

if (node == NULL){node = huffnode (-256,0);

}

root->left = node;

} else {node = root->right;

if (node == NULL){node = huffnode (-256,0);

}

root->right = node;

}

if (i == tmp->size - 1)

node->key = key;

root = node;

}

oft+=5+byte;

}

huffbufp tmp = (huffbufp) malloc (sizeof (HuffBuf));

tmp->code = buf->code+base+oft+4;

tmp->size = (int)(buf->code+base+oft);

//printf ("tmp size:% d\n",tmp->size);

result->tree = tree;

result->code = bin2str (tmp);

//printf ("% s\n",result->code);

//for (int i = 0; i code);

// }

// }

return getOriginBuf (result);

}

程式演示主檔案:

include"huffman.h"

int main {huffbufp buf = (huffbufp) malloc (sizeof (HuffBuf));

buf->code = "this is just a test!";

buf->size = strlen (buf->code);

huffresultp result = getHuffCodesByBuf (buf); // 獲取編碼結果

printf ("huffman: % s\n",result->code);

huffbufp origin = getOriginBuf (result); // 透過編碼獲取原始資料

printf ("origin: % s\n",origin->code);

return 0;

}