哈夫曼树学习笔记
1.简介
给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。 ——百度百科
也就是说,哈夫曼树是解决带权路径长度最短的好方法。
我们不妨用一个简单的问题来对哈夫曼树有更深入的理解
有n个元素,要求用2进制串表示这n个元素,且任何一个元素不是另一个元素的前缀,请问最短编码长度是?
每个元素都不是其他的元素的前缀
也就是说,每个元素都应该是一片叶子,可以把这个问题转化成一个树上路径问题。如果要求WPL最小,那么权值大的结点应该离根更近。但如何安排这些节点呢?
2.实现
我们希望点权值最大的离根尽可能近,而权值小的则会远一点。
我们可以从n个元素中,每次取出2个权值最小的节点A,B,并生成一个新的节点C,在这棵树上,C就是A,B的爸爸,c的权值为AB权值之和。将新生成的节点也放回元素集合中。
每次取最小值可以由优先队列进行优化,将复杂度由 O( n 2 n^{2} n2 ) 提升到 O( n l o g n n logn nlogn)
3.拓展——多叉哈夫曼树
好了,你已经掌握了哈夫曼树,那么请试试下面这个问题吧
[洛谷P2168 荷马史诗]([P2168 NOI2015] 荷马史诗 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))
我们能够感觉到其中贪心的味道,而且是WPL最短问题。
k进制表示这棵树是k叉的
也就是说,每次合成新的节点需要取优先队列中的前k个点
如果试试,你就会发现,喜提WA
Why?
多叉树在构建时,如果最后一次合并节点数已不足k个,那也只能合成1个节点,而根节点的儿子数不足k,显然不是最优。
因此,在构建树前,要加入m个权值为0的节点(0对WPL无贡献,WPL问题也不太可能出现节点权值为负数吧)
所以思路就有了——如果是2进制,就直接按流程计算;如果是k进制,先补充0节点,再按照流程做。
粘一份代码qwq (俺英语不好,注释如果写的有问题,轻喷)
#include<iostream>
#include<cstdio>
#include<queue>
using namespace std;
int n,k;
long long sum_val,sum_points;
struct poin{
long long sum;
int depth;
friend bool operator<(const poin &a,const poin &b){
if(a.sum!=b.sum) return a.sum>b.sum;
else if(a.sum==b.sum) return a.depth>=b.depth;
}
}poi[100005];
priority_queue<poin> q;
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++){
scanf("%lld",&poi[i].sum);
poi[i].depth=1;
q.push(poi[i]);
}
if(k==2){// just the typical huffman tree
while(!q.empty()){
poin point_1st=q.top();
q.pop();
poin point_2nd=q.top();
q.pop();
poin *tmp=new poin;
tmp->sum=point_1st.sum+point_2nd.sum;
tmp->depth=max(point_1st.depth,point_2nd.depth)+1;
sum_points+=tmp->sum;
q.push(*tmp);
if(q.size()==1)break;
}
}
else{// there's an circumstance that you need to add the poi whose val is 0 to better the huffman tree,so you need to handle this circumstance
while((n-1)%(k-1)!=0){
poin *tmp=new poin;
tmp->depth=1;
tmp->sum=0;
q.push(*tmp);
n++;
}
while(!q.empty()){
long long new_sum=0;
int new_depth=0;
for(int i=1;i<=k;i++){
poin tmp=q.top();
q.pop();
new_sum+=tmp.sum;
new_depth=max(new_depth,tmp.depth+1);
}
poin *tmp=new poin;
tmp->depth=new_depth;
tmp->sum=new_sum;
sum_points+=new_sum;
q.push(*tmp);
if(q.size()==1)break;
}
}
poin ans=q.top();
q.pop();
printf("%lld\n%d",sum_points,ans.depth-1);
return 0;
}