1. 程式人生 > 其它 >哈夫曼樹 Huffman

哈夫曼樹 Huffman

一些定義

PL

樹的路徑長度,即樹根到每個葉節點的距離之和。

WPL

樹的帶權路徑長度,即樹根到每個葉節點的距離與每個葉結點權值的乘積之和。

哈夫曼樹,也叫 Huffman 樹,就是 WPL 最短的一種最優多叉樹。

\[\]

哈夫曼樹的構造

對於哈夫曼樹的構造,我們以二叉哈夫曼樹為例:

我們每次選擇兩棵根節點權值最小的樹,將它們的根節點合併成一棵新樹。

原來的兩個根節點成為為新樹的根節點的兒子。新樹的根節點權值為合併的兩個根節點權值之和。

假設我們要構造一棵初始有五個節點的二叉哈夫曼樹,權值分別為 \(1,2,4,6,8\),則它們的構建過程如下:

(我騰訊文件沒有 VIP 所以下載不了高清圖片,只能將就看吧 qwq)

注意,哈夫曼樹中允許多個點有相同的權值。

關於 K 叉哈夫曼樹的構造

假設我們要構造一棵 \(k\) 叉哈夫曼樹 \((k>2)\)

根據以上對哈夫曼樹構造的原理,可以發現,這個構造方法是基於貪心的思想。每次取出最小的 \(k\) 個節點來合併成新的節點。

但是如果最後一步合併時,可選節點的數量不足 \(k\) 個,就會出現合併後的哈夫曼樹根節點的兒子數小於 \(k\) 的情況。

此時它就不是一顆 WPL 最小的最優 \(k\) 叉樹。因為此時我們隨便取一個葉節點當做根節點的兒子,都可使 \(\sum (w_i\times l_i)\) 的值變小。

所以我們要滿足 \((k-1)\mid (n-1)\)

這個條件時,才能使構造的 \(k\) 叉哈夫曼樹達到最優。

而要滿足這個條件,我們可以不斷向圖中加入權值為 \(0\) 的節點。顯然 \(0\) 節點一定會先合併,這樣可以使有權節點的深度降低。

\[\]

哈夫曼編碼

哈夫曼編碼的原則是:編碼從葉子節點到根節點,譯碼從根節點到葉子節點。

但其實這句話對下面的內容並沒有什麼幫助。

我們還是以二叉哈夫曼樹為例。

對於一棵二叉哈夫曼樹,我們從根節點開始,對左子樹編碼 \(0\),對右子樹編碼 \(1\),直到遍歷完整棵樹。

此時我們再從根節點出發,將路徑上的編碼排列起來,一直到某個根節點,此時的編碼串就是該根節點的哈夫曼編碼。

舉個例子,我們有一串電文 AMCADEDDMCCAD

,需要將其翻譯成 01 串,並使其儘量短。

我們統計每個字元的出現次數,可得到如下資料:

\(\begin{array}{ccc} E & M & C & A & D \\ \hline 1 & 2 & 3 & 3 & 4\\ \end{array}\)

我們將每種字元的出現次數作為節點的權值,可建造一棵如圖所示的二叉哈夫曼樹:

所以可得到各個字元的哈夫曼編碼:

\(\begin{array}{c|cc} E&001\\ M&000\\ C&01\\ A&10\\ D&11\\ \end{array}\)

根據哈夫曼編碼的定義,我們也可以看出,每種字元的編碼長度之和為 PL,而每種字元的編碼長度與其出現次數的乘積之和為 WPL。

此時的 PL 不一定是最短的,但 WPL 一定是最短的,只有這樣才能使原串翻譯後的 01 串最短。

當然上面只是說二叉哈夫曼樹,其編碼也只包含兩種字元。假若要用 \(k\) 進位制數來表示一個字串,我們可以將其建成 \(k\) 叉哈夫曼樹 \((k\ge 2)\)

對於程式碼的具體實現,結合下面的例題來說。

\[\]

例題

[NOI2015] 荷馬史詩

有一個由 \(n\) 種字元組成的字串,給出這 \(n\) 種字元的出現次數,用 \(k\) 進位制數來表示這個字串。
求出這個 \(k\) 進位制數的最短長度,並求出此時編碼最長的字元的最短編碼長度。

NOI 出模板題(

直接根據每種字元的出現次數建 \(k\) 叉哈夫曼樹。

第一問就是 WPL 的長度,第二問就是在樹中深度最大的字元的深度減一。

至於實現,我們運用優先佇列儲存每個節點的權值和深度,並按權值為第一關鍵字,深度為第二關鍵字從大到小排序。

首先將每個節點自己作為一棵樹,放到優先佇列中,並不斷加入 \(0\) 節點直到滿足條件為止。

每次取出 \(k\) 個節點來合併,同時統計 WPL。最後優先佇列中只剩一個節點,輸出統計結果與其深度減一即可。

struct node{
  int w,h;
  bool operator < (const node &a) const{
    return  a.w==w?h>a.h:w>a.w;
  }  
};

signed main(){
  n=read();k=read();priority_queue<node> q;
  for(int i=1,w;i<=n;i++) w=read(),q.push((node){w,1});
  while((q.size()-1)%(k-1)) q.push((node){0,1});
  while(q.size()>=k){
    int h=-1,w=0;
    for(int i=1;i<=k;i++){
      node t=q.top();q.pop();
      h=max(h,t.h);w+=t.w;
    }
    ans+=w;
    q.push((node){w,h+1});
  }
  printf("%lld\n%lld\n",ans,q.top().h-1);
  return 0;
}
\[\]

寫在後面

沒啦,啥都沒啦。