1. 程式人生 > 實用技巧 >哈夫曼樹學習筆記

哈夫曼樹學習筆記

定義

給定N個權值作為N個葉子結點,構造一棵二叉樹,若該樹的帶權路徑長度達到最小,稱這樣的二叉樹為最優二叉樹,也稱為哈夫曼樹(Huffman Tree)。哈夫曼樹是帶權路徑長度最短的樹,權值較大的結點離根較近。
——百度百科

應用

資訊傳遞中的編碼問題:使編碼長度儘可能短又不產生歧義。

歧義:兩個編碼有公共字首部分,比如: \(001\) , \(0010\)

構造方法

  1. 設有 n 個樹,初始時每顆樹只有一個葉子節點(有權值),代表一種編碼情況,權值代表出現次數。

  2. 每次選取權值和最小的兩顆樹,將它們合併成一棵樹,新樹權值和為原來兩顆樹權值之和。

  3. 重複 2. 直至只有一顆樹。即為哈夫曼樹。

實現方法

我們可以用小根堆堆來維護權值最小的樹,利用貪心求解。

例題

題目

洛谷 P2168 [NOI2015]荷馬史詩

思路

這道題要求構造 \(k\) 叉哈夫曼樹,並使樹的高度最小,並輸出其高度

因為每次減少 \(k-1\) 個樹,最後有些節點可能為空,應該補上一些空節點。

同時排序時要注意當權值相同時,讓高度小的樹排在後面。

程式碼

#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <string>
#include <queue>
#include <vector>

#define re(x) read(x)
#define ll long long
#define mes(x) memset(x,0,sizeof(x))
#define il inline
#define _for(i,a,b) for(int i = (a);i < (b);i++)
#define _rep(i,a,b) for(int i = (a);i <= (b);i++)

using namespace std;

template <typename T>
il void read(T &x){
	x = 0;
	T sgn  = 1;
	char ch = getchar();
	for(;!isdigit(ch);ch = getchar()) if(ch == '-') sgn = -1;
	for(;isdigit(ch);ch = getchar()) x = (x<<1) + (x<<3) +(ch^48);
	x *= sgn;
}

template <typename T>
il void write(T x){
	if(x<0) x = -x , putchar('-');
	if(x == 0) putchar(48);
	int cccnt = 0 , a[70];
	while(x>0) {
		a[++cccnt] = x%10;
		x /= 10;
	}
	for(int i = cccnt;i > 0;i--) putchar(a[i]+48);
	putchar('\n');
	return;
}

int n,k,cnt;
ll ans;

struct root{
    ll tot,h;
    bool operator < (const root &b) const {
        if(tot == b.tot) return h > b.h;
        return tot > b.tot;
    }
};

priority_queue <root> q;

int main (){
    re(n); re(k);
    _for(i,0,n){
        ll w;
        re(w);
		q.push((root){w,0});
	}
	if((n-1)%(k-1) != 0){
		cnt += k-1 - (n-1)%(k-1);
		_for(i,0,cnt)
			q.push((root){0,0});
	}
	cnt = n;
	while(cnt > 1){
		ll maxn = 0 ,tot = 0;
		_for(i,0,k){
			tot += q.top().tot;
			maxn = max(maxn,q.top().h);
			q.pop();
		}
		ans += tot;
		q.push((root){tot,maxn+1});
		cnt -= k-1;
	}
	write(ans);
	write(q.top().h);
	return 0;
}