哈夫曼树:
一棵包含n个叶子节点的k叉树,其中第i个叶子节点带有权值w[i], l[i]为该点到根节点的距离,求所有叶子节点的w[i]*l[i]之和的最小值。
二叉哈夫曼树的求解:
对于二叉哈夫曼树的求解,我们贪心的将最小的两个节点放到最远,然后合并那两个节点,新的点的权值为那两个节点的和,放入原序列中,重复上述步骤,直到原序列只剩下一个节点,这棵树就构建好了,下面有个例子
例如: 3 4 5 8 , 设最后答案为ans
首先我们选3 4, 合并节点,新点权值为7,并加入原序列,ans+= (3+4)
然后新序列中合并5和7,新点权值为12 ans+=(5+7)
最后合并12 和 8 新节点为20,跳出循环 ans+=(12+8)
最后的哈夫曼树就是右边黑色的那棵树,答案就是ans
对于某一个节点,因为其被合并之后的值给了新的节点,而新的节点合并的时候又会加上这个值,实际上是不断为答案作贡献的,做贡献次数就等于深度(也就是路径长度)
k叉哈夫曼树的求解:
对于k(k>2)叉哈夫曼树,其求解思路和2叉类似。
我们合并2叉哈夫曼树是从子节点一路合并到根节点的,结束合并操作的标志就是序列中只剩下一个数。但是对于k叉哈夫曼树这会出现问题。
因为最大的节点应该优先连在根节点上,而我们求解时又是从叶子节点开始,这会造成最后根节点的子树少于k个,这是不对的。
比如: 3叉哈夫曼树,序列:3 4 5 8
第一次合并,合并3 4 5.
第二次合并,合并剩下的,最后这棵树就是:
这显然不是最优解。
因此对于k叉哈夫曼树,为了保证其根节点可以选到k个子树,假设节点个数为n,需要满足(n-1)mod(k-1)==0 的条件,假如不满足,我们为原序列补0
对于3 4 5 8 , 它就变成 3 4 5 8 0
第一步合并3 4 0 ,新节点为7,新序列: 7 5 8
然后合并7 5 8
最后生成的树:
例题1: ch1701
题目链接:https://www.acwing.com/problem/content/150/
这就是一棵裸的二叉哈夫曼树,每合并一次相当于最后答案计算的时候多加一份,就相当于在边长为1的哈夫曼树中更深一层
代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e4 + 7;
int heap[maxn];
int cnt;
void del() {
swap(heap[1], heap[cnt--]);
int now = 1, to = 2;
while (to <= cnt) {
if (to<cnt&&heap[to]>heap[to + 1]) to++;
if (heap[to] >= heap[now]) break;
swap(heap[to], heap[now]);
now = to;
to = now << 1;
}
}
void insert(int x) {
heap[++cnt] = x;
int now = cnt;
while (now > 1) {
if (heap[now >> 1] <= heap[now]) break;
swap(heap[now], heap[now >> 1]);
now >>= 1;
}
}
int main() {
int n; cin >> n;
for (int i = 1; i <= n; i++) {
int inp;
scanf("%d", &inp);
insert(inp); //我用二叉堆维护每次选最小值
}
int ans = 0;
int min1, min2;
while (cnt > 1) {
min1 = heap[1];
del();
min2 = heap[1];
del();
ans += min1 + min2;
insert(min1+min2);
}
cout << ans << endl;
return 0;
}
例题2:bzoj4198
https://www.lydsy.com/JudgeOnline/problem.php?id=4198
每种编码方式前缀不同可以联想到字典树,此时每一个叶子节点就代表一个编号。
可以将哈夫曼树看成这样的字典树,而对于每个单词最后的贡献为: 单词哈希后长度*出现次数 ,对应到哈夫曼树里面就是 子节点权值=出现次数 , 深度=单词哈希后长度。
最后还要让最长的哈希最短,也就是要这颗哈夫曼树最深的子节点最浅,因此我们在合并的时候,假如遇到权值一样的,优先和深度小的合并(不要让深度大的继续合并下去了)
具体操作的话我们可以使用优先队列(当然堆都一样),然后用一个pair存 ,pair.first= 节点的权值 pair.second=节点的深度,优先队列按照first排,first一样再按second排
这就是一棵k叉哈夫曼树
#include<bits/stdc++.h>
#define ll long long
using namespace std;
typedef pair<ll, int> P;
const int INF = 0x3f3f3f3f;
struct cmp {
bool operator()(const P p1, const P p2) {
if (p1.first != p2.first) return p1.first > p2.first;
else return p1.second > p2.second;
}
};
priority_queue<P, vector<P>, cmp> Q;
int main() {
ll ans = 0;
int n, k;
cin >> n >> k;
for (int i = 1; i <= n; i++) {
ll inp;
scanf("%lld", &inp);
Q.push(P(inp,0));
}
while ((n - 1) % (k - 1) != 0) { //k叉哈夫曼树补0
Q.push(P(0, 0));
n++;
}
while (Q.size() > 1) {
P p = Q.top();
Q.pop();
for (int i = 2; i <= k; i++) { //每次取k,然后合并
P p1 = Q.top();
Q.pop();
p.first += p1.first;
p.second = max(p.second, p1.second);
}
p.second++;//深度为子节点里最深的+1
ans += p.first;
Q.push(p);
}
P p = Q.top();
cout << ans << endl << p.second << endl;
}