哈夫曼树+K叉哈夫曼树

目录

 

基本概念

构造方法

证明

代码

k叉哈夫曼树

例题


基本概念

问题:给定n个权值,作为n个叶结点,构造一棵二叉树,而这棵树的特点是,有n个叶节点,叶节点的值为给定的权值。而内部节点的值为子树的权值和

带权路径:WPL = \sum depth*weight

哈夫曼树是这种二叉树中带权路径最小的

构造方法

设有n个权值,W={W1,W2,...Wn},各自构成一个根为自己的二叉树

选择权值最小的两个Wi,Wj,从W中删除Wi和Wj,合并为一棵根为Wi+Wj的二叉树,加入W,即W=W-{Wi,Wj}+{Wi+Wj}

当只剩下一个的时候,结束,剩下的那个二叉树,就是哈夫曼树

证明

引理:假设W1和W2是最小的两个权值,则他们一定是深度最深的分支节点的孩子

证明:假设不是W1和W2,而是Wx和Wy,设W1和W2的深度为p(或者p1和p2),Wx和Wy的深度为q,显然p<=q,且W1,W2<=Wx,Wy

WPL1=X+p*(W1+W2)+q*(Wx+Wy) (X是除了这4个节点以外的带权路径)

交换W1和Wx,W2和Wy 

WPL2=X+q*(W1+W2)+p*(Wx+Wy)

WPL1-WPL2=(p-q)(W1+W2)-(p-q)*(Wx+Wy)=(p-q)*(W1+W2-Wx-Wy)>=0

即交换后更小,矛盾

得证

 

接着用归纳法证明这个算法成立:

n=2时显然成立

假设n=k时成立

当n=k+1时,根据引理,将最小的两个权值W1+W2合并成W1+W2,那这时候问题就转化成n=k时的情况,所以成立

代码

poj3253

其实就是搞个小根堆,然后每次弹两个最小的,加一下,放回去

#include<iostream>
#include<queue>
using namespace std;
int main(){
    typedef long long int LL;
    priority_queue<LL,vector<int>,greater<LL> > q;
    int n;
    scanf("%d",&n);
    while(n--){
        LL t;
        scanf("%lld",&t);
        q.push(t);
    }
    LL ans=0;
    while(q.size()>1){
        LL x=q.top();
        q.pop();
        LL y=q.top();
        q.pop();
        ans+=x+y;
        q.push(x+y);
    }
    printf("%lld\n",ans);
    return 0;
}

k叉哈夫曼树

k叉哈夫曼树是一个度只有0或者n的树,意味着

n_{0}+n_{k} = k*n_{k}+1 \\ n_{k} = \frac{n_{0}-1}{k-1}

所以假设n个叶子,则n-1要被k-1整数,所以如果不能整除要补上一些虚节点,权值为0。

需要补 k-1 - \frac{n_{0}-1}{k-1}

构造方法和2叉的时候一样,每次选k个最小的

#include<iostream>
#include<queue>
#include<algorithm>
#include<functional>
using namespace std;
const int N=100005;

int a[N],n;
int k_huff(int k){
    int cost=0;
    priority_queue<int,vector<int>,greater<int> > q;
    for(int i=0;i<n;++i)q.push(a[i]);
    bool first=true;
    while(q.size()>1){
        int cnt;
        if(!first){
            cnt=k;
        }
        else{
            first=false;
            if((n-1)%(k-1)==0){
                cnt=k;
            }
            else{
                cnt=(n-1)%(k-1)+1;
            }
        }
        int sum=0;
        while(cnt--){
            sum+=q.top();
            q.pop();
        }
        cost+=sum;
        q.push(sum);
    }
    return cost;
}

例题

hdu5884

这题简单来说就是问WPL小于等于指定数的k叉哈夫曼树最小的k是多少

这题如果你一次一次循环上去,复杂度是O\left(n*n \log_{2} n \right )

如果二分,复杂度是O\left(n \log_{2} n \log_{2} n\right )

所以我们换一种方法

假设权值放在a数组里,我们再开一个数组b

对a数组排序

用两个指针,一个指向a,一个指向b,然后哪个小,取哪个

选择出节点后,加入b数组,有点类似于归并

复杂度是O\left(n \log_{2} n + 2n \log_{2} n\right )= O\left(n \log_{2} n \right )

#include<iostream>
#include<algorithm>
using namespace std;
const int N=100005;
int a[N],b[N],n;
bool k_huff(int k,int cost){
    int i=0;
    int j=0,b_size=0;
    bool first=true;
    while(b_size-j+n-i>1){
        int cnt;
        if(!first)cnt=k;
        else{
            first=false;
            if((n-1)%(k-1)==0){
                cnt=k;
            }
            else{
                cnt=(n-1)%(k-1)+1;
            }
        }
        int sum=0;
        while(cnt--){
            if(i==n)sum+=b[j++];
            else if(j==b_size)sum+=a[i++];
            else if(a[i]<=b[j])sum+=a[i++];
            else sum+=b[j++];
        }
        cost-=sum;
        if(cost<0)return false;
        b[b_size++]=sum;
    }

    return true;
}
int main(){
    int t,T;
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&T);
        for(int i=0;i<n;++i)scanf("%d",&a[i]);
        if(n<=1){
            printf("1\n");
        }
        else{
            sort(a,a+n);
            int low=2,high=n;
            while(low<=high){
                int mid=(low+high)>>1;
                if(k_huff(mid,T)){
                    high=mid-1;
                }
                else{
                    low=mid+1;
                }
            }
            printf("%d\n",high+1);
        }
    }
    return 0;
}

 

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Nightmare004

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值