哈夫曼樹 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)\)
而要滿足這個條件,我們可以不斷向圖中加入權值為 \(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;
}
\[\]寫在後面
沒啦,啥都沒啦。