轉自:https://blog.csdn.net/shuangde800
關鍵思想:
依據哈弗曼樹的定義,一棵二叉樹要使其WPL值最小,必須使權值越大的葉子結點越靠近根結點,而權值越小的葉子結點越遠離根結點。
哈弗曼根據這一特點提出了一種構造最優二叉樹的方法,其基本思想如下:
1。根據給定的n個權值{w1, w2, w3 ... wn },構造n棵只有根節點的二叉樹,令起權值為wj
2。在森林中選取兩棵根節點權值最小的樹作為左右子樹,構造一顆新的二叉樹,置新二叉樹根節點權值為其左右子樹根節點權值之和。注意,左子樹的權值應小於右子樹的權值。
3。從森林中刪除這兩棵樹,同時將新得到的二叉樹加入森林中。(換句話說,之前的2棵最小的根節點已經被合並成一個新的結點了)
4。重復上述兩步,直到只含一棵樹為止,這棵樹即是 哈弗曼樹
以下演示了用Huffman算法構造一棵Huffman樹的過程:
考研題目:

三、哈夫曼樹的在編碼中的應用
在電文傳輸中,須要將電文中出現的每一個字符進行二進制編碼。在設計編碼時須要遵守兩個原則:
(1)發送方傳輸的二進制編碼,到接收方解碼后必須具有唯一性,即解碼結果與發送方發送的電文全然一樣;
(2)發送的二進制編碼盡可能地短。以下我們介紹兩種編碼的方式。
1. 等長編碼
這樣的編碼方式的特點是每一個字符的編碼長度同樣(編碼長度就是每一個編碼所含的二進制位數)。如果字符集僅僅含有4個字符A,B,C,D,用二進制兩位表示的編碼分別為00,01,10,11。若如今有一段電文為:ABACCDA,則應發送二進制序列:00010010101100,總長度為14位。當接收方接收到這段電文后,將按兩位一段進行譯碼。這樣的編碼的特點是譯碼簡單且具有唯一性,但編碼長度並非最短的。
2. 不等長編碼
在傳送電文時,為了使其二進制位數盡可能地少,能夠將每一個字符的編碼設計為不等長的,使用頻度較高的字符分配一個相對照較短的編碼,使用頻度較低的字符分配一個比較長的編碼。比如,能夠為A,B,C,D四個字符分別分配0,00,1,01,並可將上述電文用二進制序列:000011010發送,其長度僅僅有9個二進制位,但隨之帶來了一個問題,接收方接到這段電文后無法進行譯碼,由於無法斷定前面4個0是4個A,1個B、2個A,還是2個B,即譯碼不唯一,因此這樣的編碼方法不可使用。
因此,為了設計長短不等的編碼,以便降低電文的總長,還必須考慮編碼的唯一性:即在建立不等長編碼時必須使不論什么一個字符的編碼都不是還有一個字符的前綴,這宗編碼稱為前綴編碼(prefix code)
(1)利用字符集中每一個字符的使用頻率作為權值構造一個哈夫曼樹;
(2)從根結點開始,為到每一個葉子結點路徑上的左分支賦予0,右分支賦予1,並從根到葉子方向形成該葉子結點的編碼
例題:
如果一個文本文件TFile中僅僅包括7個字符{A,B,C,D,E,F,G},這7個字符在文本中出現的次數為{5,24,7,17,34,5,13}
利用哈夫曼樹能夠為文件TFile構造出符合前綴編碼要求的不等長編碼
詳細做法:
1. 將TFile中7個字符都作為葉子結點,每一個字符出現次數作為該葉子結點的權值
2. 規定哈夫曼樹中全部左分支表示字符0,全部右分支表示字符1,將依次從根結點到每一個葉子結點所經過的分支的二進制位的序列作為該
結點相應的字符編碼
3. 因為從根結點到不論什么一個葉子結點都不可能經過其它葉子,這樣的編碼一定是前綴編碼,哈夫曼樹的帶權路徑長度正好是文件TFile編碼
的總長度
通過哈夫曼樹來構造的編碼稱為哈弗曼編碼(huffman code)

#include<iostream>
#include<cstdio> #include<cstring> using namespace std; #define N 10 // 帶編碼字符的個數,即樹中葉結點的最大個數 #define M (2*N-1) // 樹中總的結點數目 class HTNode{ // 樹中結點的結構 public: unsigned int weight; unsigned int parent,lchild,rchild; }; class HTCode{ public: char data; // 待編碼的字符 int weight; // 字符的權值 char code[N]; // 字符的編碼 }; void Init(HTCode hc[], int *n){ // 初始化,讀入待編碼字符的個數n,從鍵盤輸入n個字符和n個權值 int i; printf("input n = "); scanf("%d",&(*n)); printf("\ninput %d character\n",*n); fflush(stdin); for(i=1; i<=*n; ++i) scanf("%c",&hc[i].data); printf("\ninput %d weight\n",*n); for(i=1; i<=*n; ++i) scanf("%d",&(hc[i].weight) ); fflush(stdin); }// void Select(HTNode ht[], int k, int *s1, int *s2){ // ht[1...k]中選擇parent為0,而且weight最小的兩個結點,其序號由指針變量s1,s2指示 int i; for(i=1; i<=k && ht[i].parent != 0; ++i){ ; ; } *s1 = i; for(i=1; i<=k; ++i){ if(ht[i].parent==0 && ht[i].weight<ht[*s1].weight) *s1 = i; } for(i=1; i<=k; ++i){ if(ht[i].parent==0 && i!=*s1) break; } *s2 = i; for(i=1; i<=k; ++i){ if(ht[i].parent==0 && i!=*s1 && ht[i].weight<ht[*s2].weight) *s2 = i; } } void HuffmanCoding(HTNode ht[],HTCode hc[],int n){ // 構造Huffman樹ht,並求出n個字符的編碼 char cd[N]; int i,j,m,c,f,s1,s2,start; m = 2*n-1; for(i=1; i<=m; ++i){ if(i <= n) ht[i].weight = hc[i].weight; else ht[i].parent = 0; ht[i].parent = ht[i].lchild = ht[i].rchild = 0; } for(i=n+1; i<=m; ++i){ Select(ht, i-1, &s1, &s2); ht[s1].parent = i; ht[s2].parent = i; ht[i].lchild = s1; ht[i].rchild = s2; ht[i].weight = ht[s1].weight+ht[s2].weight; } cd[n-1] = '\0'; for(i=1; i<=n; ++i){ start = n-1; for(c=i,f=ht[i].parent; f; c=f,f=ht[f].parent){ if(ht[f].lchild == c) cd[--start] = '0'; else cd[--start] = '1'; } strcpy(hc[i].code, &cd[start]); } } int main() { int i,m,n,w[N+1]; HTNode ht[M+1]; HTCode hc[N+1]; Init(hc, &n); // 初始化 HuffmanCoding(ht,hc,n); // 構造Huffman樹,並形成字符的編碼 for(i=1; i<=n; ++i) printf("\n%c---%s",hc[i].data,hc[i].code); printf("\n"); return 0; }