哈夫曼树
哈夫曼树是一种二叉树,其带权路径(叶子结点的权重与其到根节点的路径长度之积)和最小。
通俗一点说,哈夫曼树的每个叶子结点都有一个权重,而每个叶子结点的带权路径,就是其权重与到根节点的距离的乘积。哈夫曼树要求所有叶子结点的带权路径和最小。
怎么构造?虽说哈夫曼树是一个二叉树,但是可以根据二叉树的原理扩展为k叉树,下面先介绍二叉树的构造方法,k叉树就显而易见了。
运用贪心的思想,肯定是权重越大的节点越靠近根节点是最优的。我们可以先将权重排序,由小到大、由下及上构造。每次选取权重最小的两个节点,将其连接到一个父节点上,父节点的权重为子节点权重之和,然后将父节点放回到数组中,重复这个过程,直到构造出树。
考虑k叉树,显然每次选取k个最小的连接到一个父节点上。但是k叉会有一个问题:每一个节点的子节点并不是满k叉的(读者可自证为什么二叉树不会有这个问题),但是反观我们的构造方法,是优先构造底层,而显然越靠近根节点的层越满越优,所以之前的构造方法就会有问题。这里就需要将不够的地方补齐,手动添加一下叶子结点,至于其权重,弄成0就好啦。
哈夫曼编码
考虑一个k叉树,根据每一层选择的节点不一样,可以表示出k进制数,哈夫曼编码便是运用这个道理。将每一个需要编码的元素以其出现次数为权重,构建哈夫曼树,自然编码也就出来了。
代码网上比较多,这里就不放了,放一道哈夫曼意识流的题qwq
[[NOI2015] 荷马史诗]([P2168 NOI2015] 荷马史诗 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))
#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
long long read() {
long long x = 0; int f = 0; char c = getchar();
while (c < '0' || c > '9') f |= c == '-', c = getchar();
while (c >= '0' && c <= '9') x = (x << 3) + (x << 1) + (c - '0'), c = getchar();
return f ? -x : x;
}
int n, k;
struct szh {
int h;
long long w;
szh(long long x, int y){
w = x; h = y;
}
bool operator < (const szh b) const {
if (w != b.w) return w > b.w;
return h > b.h;
}
};
long long ans;
priority_queue<szh> q;
int main() {
n = read(); k = read();
for (int i = 1; i <= n; ++i) {
long long x = read();
q.push(szh(x, 1));
}
int cnt = 0;
if ((n - 1) % (k - 1)) cnt = k - 1 - (n - 1) % (k - 1);
for (int i = 1; i <= cnt; ++i) q.push(szh(0, 1));
cnt += n;
while (cnt > 1) {
long long sum = 0;int height = 0;
for (int i = 1; i <= k; ++i) {
sum += q.top().w;
height = max(height, q.top().h);
q.pop();
}
q.push(szh(sum, height + 1));
ans += sum;
cnt -= k - 1;
}
cout << ans << endl << q.top().h - 1;
}