哈夫曼樹
哈夫曼(huffman)樹,又稱為 最優樹 ,是一類帶權路徑長度最短的樹,有著廣泛的應用,常用的jpeg格式就是採用哈夫曼樹進行編碼壓縮的。在這裡我們將要討論哈夫曼二叉樹(最優二叉樹)的概念及其演算法。
1.問題的引入
將樹結構用於實際,常常要考慮乙個問題,即如何設計一棵二叉樹,使得執行路徑最短,即演算法的效率最高。
例如:現有一批球磨機上的鐵球,需要將它分成四類:直徑不大於20的屬於第一類;直徑大於20而不大於50的屬於第二類;直徑大於50而不大於100的屬於第三類;其餘的屬於第四類;假定這批球中屬於第
一、二、三、四類鐵球的個數之比例是1:2:3:4。
我們可以把這個判斷過程表示為下圖中的兩種方法:
那麼究竟將這個判斷過程表示成哪乙個判斷框,才能使其執行時間最短呢?讓我們對上述判斷框做一具體的分析。
假設有1000個鐵球,各類鐵球的個數分別為:100、200、300、400;
對於圖(a)和 圖(b)比較的次數分別如下表所示:
圖(a)
圖(b)
序號 比較式
比較次數
序號 比較式
比較次數
1 a<=20
1000
1 a>100
1000
2 a<=50
900
2 a>50
600
3 a<=100
700
3 a<=20
300
合計 2600
合計 1900
過上述分析可知,圖(b)所示的判斷框的比較次數遠遠小於圖(a)所示的判斷框的比較次數。為了找出比較次數最少的判斷框,將涉及到樹的路徑長度問題。
2.二叉樹的路徑長度
從樹中乙個結點到另乙個結點之間的分支構成這兩個結點之間的路徑,路徑上的分支數目稱做路徑長度。樹的路徑長度是從樹根每乙個結點的路徑長度之和。這種路徑長度最短的二叉樹是一棵完全二叉樹。
帶權(二叉)樹是指每個結點都帶有乙個權值的(二叉)樹。
若將路徑長度的概念推廣到一般情況,考慮帶權的結點。結點的帶權路徑長度為從該結點到樹根之間的路徑長度與結點上權的乘積。樹的帶權路徑長度為樹中所有葉子結點的帶權路徑長度之和,通常記作:
例如對於上圖,可以將圖中的方框看成是對二叉樹的擴充,在這裡方框成為二叉樹的葉子結點。 對於圖(a) 和圖(b)各葉子結點的權值和路徑長度分別為:
圖(a)
圖(b)
類別 一
二 三
四 序號
一 二
三 四
權值 1
2 3
4 權值
1 2
3 4
路徑長度
1 2
3 3
路徑長度
3 3
2 1
wpl=1*1+2*2+3*3+4*3=26
wpl=1*3+2*3+3*2+4*1=19
從這裡可以看出,找到乙個使wpl最小的二叉樹,是解決問題的關鍵。
假設有n個權值(w1,w2,…,wn),試構造一棵有n個葉子結點的二叉樹,每個葉子結點帶權為wi,則其中帶權路徑長度wpl最小的二叉樹稱做最優二叉樹或哈夫曼二叉樹。在上述例子中,圖(b)所示的二叉樹,就是最優二叉樹,即哈夫曼二叉樹。
哈夫曼樹的最主要的特點是帶權的二叉樹的路徑長度最小的是權大的葉子離根最近的二叉樹,因此哈夫曼樹又稱為最優葉子二叉樹。
3.哈夫曼樹的構造
如何構造最優葉子二叉樹?d?a?huffman給出了乙個簡單而又漂亮的演算法,這個演算法稱為哈夫曼演算法。它的基本思想是要讓權大的葉子離根最近。具體作法是:
(1)根據給定的n個權值{w1,w2,…,wn},構造n棵二叉樹的集合f={t1,t2,…,tn},其中每棵二叉樹中均只含乙個帶權值為wi的根結點,其左、右子樹為空樹;
(2)在f中選取其根結點的權值為最小的兩棵二叉樹,分別作為左、右子樹構造一棵新的二叉樹,並置這棵新的二叉樹根結點的權值為其左、右子樹根結點的權值之和;
(3)從f中刪去這兩棵樹,同時加入剛生成的新樹;
(4)重複(2)和(3)兩步,直到f中只含一棵樹為止。
從上述演算法中可以看出,f實際上是森林,該演算法的思想是不斷地進行森林f中的二叉樹的「合併」,最終得到哈夫曼樹。
實際上,哈夫曼演算法的實現與實際問題所採用的儲存結構有關。現假設用陣列f來儲存哈夫曼樹,其中第i個陣列元素f[i]是哈夫曼樹中的乙個結點,其位址為i,有3個域,data域存放該結點的權值,lchild域和rchild域分別存放該結點左、右子樹的根結點的位址(下標)。在初始狀態下: f[i].data=wi,f[i].lchild=f[i].rchild=0,i=1,2,…,n。
即先構造了n個方形葉子。在以後每步構造一棵新二叉樹時,都需要對森林中所有二叉樹的根結點進行排序,因此可用陣列a作為排序暫存空間,其中第i個陣列元素a[i]是森林f中第i棵二叉樹的根結點,有2個域,data是根結點所對應的權值,addr是根結點在f中的位址(下標)。在初始狀態下:a[i].data=wi,a[i].addr=i,i=1,2,…n。
【樹】哈夫曼樹(一)
time limit:1000ms memory limit:65536k
total submit:72 accepted:43
description
假設用於通訊的電文僅由8個字母組成,字母在電文中出現的頻率分別為7、19、2、6、32、3、21、10。試為這8個字母設計哈夫曼編碼。如果用二進位制數表示這8個字母的編碼方案.(請按照左子樹根節點的權小於等於右子樹根節點的權的次序構造)
input
第一行為字母的個數n;
第二行至第n+1行分別為各個字母在電文中出現的頻率;
output
按照中序遍歷輸出各個編碼
sample input
871926323
2110
sample output
19:0021:01
2:10000
3:10001
6:1001
7:1010
10:1011
32:11
var
i,j:longint;
begin
readln(n);
for i:=1 to n do
begin
readln(a[i].data);
f[i].data:=a[i].data;
a[i].addr:=i;
end;
end;
procedure sort(n:longint);
var i,j:longint;
t:arr;
begin
for i:=1 to n-1 do
for j:=i+1 to n do
if a[i].data>a[j].data then
begin
t:=a[i];
a[i]:=a[j];
a[j]:=t;
end;
end;
procedure dfs(x:longint; s:string);
var i,j:longint;
begin
if f[x].lchild<>0 then dfs(f[x].lchild,s+'0');
if f[x].rchild<>0 then dfs(f[x].rchild,s+'1');
if (f[x].lchild=0) and (f[x].rchild=0) then writeln(f[x].data,':',s);
end;
begin
init;
t:=n+1;
i:=n;
while i>1 do
begin
sort(i);
f[t].data:=a[1].data+a[2].data;
f[t].lchild:=a[1].addr;
f[t].rchild:=a[2].addr;
a[1].data:=f[t].data;
a[1].addr:=t;
a[2].data:=a[i].data;
a[2].addr:=a[i].addr;
inc(t); dec(i);
end;
dfs(t-1,'');
end.
19:0021:012:100003:100016:10017:101010:101132:11
哈弗曼編碼 哈弗曼樹
哈弗曼編碼是依賴於字元使用頻率來建立的一種編碼,通過把使用頻率低的字元分配相對較多的01編碼,而使用頻率高的分配相對較低的01編碼,來建立最小的帶權路徑長度的樹,來最大化的獲得編碼儲存空間的一種編碼規則。這個樹稱為哈弗曼樹,也稱為最優二叉樹。這樣可以確定每乙個字元的編碼不可能成為其他字元編碼的坐子串...
哈弗曼樹與哈弗曼編碼(實現)
歷史背景 1951年,霍夫曼在mit攻讀博士學位,他和修讀資訊理論課程的同學得選擇是完成學期報告還是期末考試。導師robert fano出的學期報告題目是 查詢最有效的二進位制編碼。由於無法證明哪個已有編碼是最有效的,霍夫曼放棄對已有編碼的研究,轉向新的探索,最終發現了基於有序頻率二叉樹編碼的想法,...
哈弗曼壓縮原理
1 根據輸入的字串,整理得到其權值表 佇列,權值表元素和哈弗曼樹節點可以定義為乙個類,方便建樹。2 建立哈弗曼樹,用遞迴遍歷樹,設左1右0,得到碼表佇列。3 根據碼表把字串轉換成由01組成的串。4 把01串按照二進位制轉換成十進位制的方法,把每16個01串轉換成乙個整數。最後不足16個的補零至16個...