哈夫曼樹,最優二叉樹,帶權路徑長度(wpl)最短的樹。它沒有度為1的點,是一棵嚴格的二叉樹(滿二叉樹)。
了解哈夫曼樹,我們首先要知道樹的幾個相關術語,並了解什麼是wpl。
注:樹的wpl這個概念非常重要,這個公式直接產生了哈夫曼編碼資料壓縮的應用
根據給定的n個權值構成n棵二叉樹的集合f=,其中每棵二叉樹t
it_i
ti中只有乙個帶權為w
iw_i
wi的根結點,其左右子樹均空
在f中選取兩棵根結點的權值最小的樹作為左右子樹構造一棵新的二叉樹,且置新的二叉樹的根結點的權值為其左。右子樹上根結點的權值之和。
在f中刪除這兩棵樹,同時將新得到的二叉樹加入f中。
重複(2)(3),直到f只含一棵樹為止。這棵樹即為哈夫曼樹
在談論哈夫曼編碼之前,我們先來了解一下編碼的相關概念。
等長的二進位制編碼
對於乙個無記憶離散信源中每乙個符號,若採用相同長度的不同碼字代表相應的符號,就稱為等長編碼。如:a/b/c/d採用00/01/10/11編碼就是乙個等長的編碼。
不等長的二進位制編碼
在資料傳輸過程中,我們需要壓縮檔案,就可以通過不等長編碼的方式。
哈夫曼編碼就是一種不等長的編碼,它根據字頻分析結果決定每個字元的編碼長度,出現概率高的字元編碼短,出現概率低的編碼長。哈夫曼研究這種最優樹的目的是為了解決當年遠距離通訊(主要是電報)的資料傳輸的最優化問題。
但是檔案壓縮了之後我們需要解壓才能得到想要的檔案,編碼了之後我們還需解碼。下面我們來看一下乙個不等長編碼的二進位制編碼的示例:同樣我們給對a/b/c/d編碼,分別為0/00/1/01,很容易看出如果我們收到電文0000,我們則無法得到唯一的譯文(0000可以翻譯成aaaa、bb等等
)
一般來說,若要實現無失真的編碼,這不但要求信源符號與碼字是一一對應的,而且要求碼符號串行的反變換也是唯一的。也就是說,乙個碼的任意一串有限長的碼符號串行(碼字)只能被唯一地翻譯成所對應的信源符號串行。不然我們的譯文就會有二義性,這樣的編碼是沒有意義的。
為了解決不等長二進位制編碼解碼的二義性問題,我們需要引入乙個概念字首碼
字首碼是一種不等長的編碼,它保證任何乙個字元的編碼都不是另乙個編碼的字首。比如a(0),b(10),c(110),d(1111)就是一組字首碼。
在編寫乙個具體的演算法之前,我們總要先思考演算法的載體——資料如何被儲存。哈夫曼演算法的關鍵資料結構為哈夫曼樹,所以接下來我們來思考哈夫曼樹的儲存。
哈夫曼樹的儲存
我們採用孩子雙親表示法儲存哈夫曼樹
typedef
struct
htnode,
*huffmantree;
//動態分配陣列儲存哈夫曼樹
定義乙個char型二級指標儲存哈夫曼編碼
typedef
char
**huffmancode;
哈夫曼樹的構造
哈夫曼樹的構造思想就是利用二叉樹的非葉子結點來實現二叉樹的結構(:我在說什麼
void
huffmancoding
(huffmantree &ht, huffmancode &hc,
int*w,
int n)
;for
(; i <= m;
++i;
++p)
*p =
;// 構造哈夫曼樹
for(i = n+
1; i <= m;
++i)
}
下面對這段**的一些細節略加解釋:傳參時使用引用實現了直接對函式外變數的修改,解決了c函式按值傳遞且只有單返回值的弊端
*w表示待編碼的字元陣列,n表示字元個數,m=2n-1表示哈夫曼樹的結點個數,至於分配m+1個結點
的空間,則是為了將結點標號和陣列下標對應起來,陣列的0號元素棄置不用(樹的常見儲存
)
malloc函式的返回值為void*,需要將其強轉成htnode的指標,即huffmantree型別
huffmantree p
是為遍歷並初始化動態分配的記憶體塊而設的htnode指標
,int i
是為了迴圈而設的輔助變數
哈夫曼編碼
以下**依據從葉子到根逆向求每個字元的哈夫曼編碼
// 哈夫曼編碼
hc =
(huffmancode)
malloc
((n+1)
*sizeof
(char*)
);cd =
(char*)
malloc
(n*sizeof
(char))
;cd[n-1]
="\0"
;for
(int i =
1; i <= n;
++i)
free
(cd)
;
註記:同理,開闢 n+1個char* 的空間為了將陣列下標i與第i個待編碼的字元對應起來
cd為輔助陣列,用於給單個字元編碼,由於一開始不知道每個字元的編碼長度,所以使用cd開足夠大的編碼空間n,最後將編碼完成後就將確定長度的編碼串拷貝到哈夫曼表hc
中
最後獻上huffman編碼演算法的完整**
typedef
struct
htnode,
*huffmantree;
//哈夫曼樹結點和根結點
typedef
char
**huffmancode;
//哈夫曼編碼表
void
huffmancoding
(huffmantree &ht, huffmancode &hc,
int*w,
int n)
;for
(; i <= m;
++i;
++p)
*p =
;// 構造哈夫曼樹
for(i = n+
1; i <= m;
++i)
// 哈夫曼編碼
hc =
(huffmancode)
malloc
((n+1)
*sizeof
(char*)
);cd =
(char*)
malloc
(n*sizeof
(char))
; cd[n-1]
="\0"
;for
(int i =
1; i <= n;
++i)
free
(cd)
;}
習題
例1 電文的解碼:分解電文中字串,從根結點出發,按字元0/1確定左、右孩子,直到葉子結點。
char text[maxn]
="101110110111"
;for
(int i =
0; i < text.
length()
; i++
)}
例2 已知某系統在通訊聯絡中只可能出現8種字元,其概率分布為0.05, 0.29, 0.07, 0.08, 0.14, 0.23, 0.03, 0.11, 試設計哈夫曼編碼。
分析:首先設8個字元的權分別為w=, n = 8, 則m = 15, 即在有8個葉子結點的哈夫曼樹上有15個結點,然後構造一棵哈夫曼樹,最後就得到哈夫曼編碼。
其實哈夫曼樹使用場景還真不少,例如apache負載均衡的按權重請求策略的底層演算法、咱們生活中的路由器的路由演算法、利用哈夫曼樹實現漢字點陣字形的壓縮儲存、快速檢索資訊等等底層優化演算法,其實核心就是因為目標帶有權重、長度遠近這類資訊才能構建赫夫曼模型。
ps:哈夫曼樹最優子結構性質的證明
可以詳見這篇博文演算法學習之哈夫曼編碼演算法
哈夫曼樹及哈夫曼編碼
給定n個權值作為n個葉子結點,構造一棵二叉樹,若帶權路徑長度達到最小,稱這樣的二叉樹為最優二叉樹,也稱為哈夫曼樹 huffman tree 哈夫曼樹是帶權路徑長度最短的樹,權值較大的結點離根較近。樹節點間的邊相關的數叫做權。從樹中的乙個節點到另乙個節點之間的分支構成兩個點之間的路徑,路徑上的分支數目...
哈夫曼編碼 哈夫曼樹
1.定義 哈夫曼編碼主要用於資料壓縮。哈夫曼編碼是一種可變長編碼。該編碼將出現頻率高的字元,使用短編碼 將出現頻率低的字元,使用長編碼。變長編碼的主要問題是,必須實現非字首編碼,即在乙個字符集中,任何乙個字元的編碼都不是另乙個字元編碼的字首。如 0 10就是非字首編碼,而0 01不是非字首編碼。2....
哈夫曼樹 哈夫曼編碼
定義從a結點到b結點所經過的分支序列為從a結點到b結點的路徑 定義從a結點到b結點所進過的分支個數為從a結點到b結點的路徑長度 從二叉樹的根結點到二叉樹中所有結點的路徑長度紙盒為該二叉樹的路徑長度 huffman樹 帶權值路徑長度最小的擴充二叉樹應是權值大的外界點舉例根結點最近的擴充二叉樹,該樹即為...