Huffman树总结

落爺英雄遲暮 提交于 2020-03-09 20:05:46

其实我觉得这个东西还是比较玄学,主要是实现起来感觉不是难,重要的是学会如何建模.

定义

构造一棵树使一棵树中点的权值与深度的乘积和最小
\(Min\sum_{i}val[i]*dep[i](i∈V)\)
那么我们如何构造一棵这样的树呢?显然对于每一个点,只有权值是给出的,我们yy一下就发现,只有当权值大的树的深度尽量小那么才能够保证权值和比较小.所以我们可以用优先队列|堆完成这个任务
但是一般的题目不会让你写Huffman树的裸题,那么显然,构造出来的树的分叉也不一定只有两个,所以我们要掌握很多东西.

结论1

若一颗Huffman树是满k叉树,那么显然(n-1)%(k-1)必然为0.

这个的证明其实还是比较简单,因为你将根节点除去后的图必然有k的倍数个分叉,这才满足是一个满k叉树

结论2

权值越大的放到深度小的才能够构成Huffman树.

这个就是依据Huffman树的定义来的.

模板

讲了这么多,那么Huffman树究竟有没有一个固定的模板呢?答案是有的

struct node{
    int val,dep;
    bool operator<(node b){
        return val>b.val || val==b.val && dep>b.dep;//显然深度大的如果不先合并它就会变得越来越大!
    }
}
priority_queue<node>q;
void Huffman(int n,int k){//一共有n个点,k叉Huffman树
    ll sum=0;
    if((n-1)%(k-1))sum=(k-1)-(n-1)%(k-1);//需要补充的节点
    for(int i=1;i<=sum;i++){
        node need;
        need.dep=1;need.val=0;//补充val为0的节点显然对答案没有影响.
        q.push(need);
    }
    sum+=n;
    while(sum>1){//还没到根节点
        node a;int mx,now=0;
        for(int i=1;i<=k;i++){
            node now=q.top();mx=max(mx,q.dep);
            now+=q.val();q.pop();
        }
        a.val=now;ans+=now;//合并后的权值
        a.dep=mx+1;q.push(a);//合并后的深度,搞好后放进堆
        sum-=(k-1)//搞好了k-1个节点
    }
    //最后的合并的结果存储在ans里面
}

例题1 NOIP2004 合并果子

原题


这道题目显然是Huffman树的裸题,我们每次搞两个数合并,然后合并出来的树权值变大,那么最优解就满足Huffman树的定义,那么就可以这么搞.

#include<stdio.h>
#include<stdlib.h>
#include<queue>
#define re register
#define ll long long
using namespace std;
int n;
priority_queue<int >q;
int main(){
    scanf("%d",&n);
    for(re int i=0;i<n;i++){
        int a;scanf("%d",&a);
        q.push(-a);
    }
    int ans=0;
    while(q.size()>1){
        int del1=q.top();q.pop();
        int del2=q.top();q.pop();
        ans-=del1+del2;
        q.push(del1+del2);//这里维护的是2叉
    }
    printf("%d\n",ans);
    return 0;
}

例题2 NOI2015 荷马史诗

原题


显然为了使总长度最小,且每一个点有属于自己的val,很容易想到Huffman树,那么我们就可以理所当然的构造一棵,然后在这颗Huffman树里寻找出需要的答案.

#include<stdio.h>
#include<algorithm>
#include<queue>
#define re register
#define ll long long
using namespace std;
struct node{
    ll dep,val;
    bool operator<(node b)const{
        return val>b.val || val==b.val && dep>b.dep;
    }
};
priority_queue<node>q;
ll n,k;
int main()
{
    scanf("%lld%lld",&n,&k);
    for(re ll i=1;i<=n;i++){
        node a;a.dep=1;
        scanf("%lld",&a.val);
        q.push(a);
    }
    ll bu=0;
    if((n-1)%(k-1)==0);
    else bu+=k-1-(n-1)%(k-1);
    for(re ll i=1;i<=bu;i++){
        node a;a.dep=1;a.val=0;
        q.push(a);
    }
    bu+=n;ll ans=0;
    while(bu>1){
        node a;ll redep=0,nowa=0;
        for(re ll i=1;i<=k;i++){
            node now=q.top();nowa+=now.val;
            redep=max(redep,now.dep);q.pop();
        } 
        bu-=k-1;
        ans+=nowa;a.dep=redep+1;
        a.val=nowa;
        q.push(a);
    }
    printf("%lld\n%lld\n",ans,q.top().dep-1);
    return 0;
}

习题(待补充)

总结

那么这一类题目在题面中一般会告诉我们k和要我们求出一个最小值,那么这就是Huffman树的适用范围,然后更多的话就需要在题目中自行探索了.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!