k叉哈夫曼树

有n个数(即n个叶子节点),构造k叉(k>=2)哈夫曼树的方法;

构造哈夫曼树,其实就是不停的“合并”的过程。并且每次合并,我们都是取前k个最小的数。算法的主要复杂就在于如何取前k个最小的数;不停排序或者优先队列、堆都可以做到,但复杂都接nlogn,那么我们可不可以只排序一次就能每次都取出前k个数呢?

当然可以

我们可以维护两个数组利用类似于“归并排序”的想法,O(n)的构造出k叉哈夫曼树。

第一步:先将n个数从小到大排序一次,放入第一个数组a;取k个数合并成一个新的数放入数组b的末尾。

之后,每次从两个数组里挑选出k个最小的数合并再次放到数组b的末尾。

这里不难发现:b数组是有序的。因为每次放入的都是最小的k个数之和,第二次放的肯定比第一次大。

那么也就是a,b数组都是有序的,所以之前说的从两个数组里取k个最小的数出来也就不难做到,只要维护两个指针,都指向数组的第一个位置,然后每次比较谁小就取谁,被取的那个指针往后移一位。

最后,当a数组为空,b数组只剩一个数时算法结束。(这个数即哈夫曼的根)

在以上的描述中,为了方便我省略了一个细节:

注意到,每次我们都是取k个数,但在最后一次取的时候,可能已经不足k个数可取了,对于这里,有两种处理方法,第一种是补充哈夫曼树也可以是k叉的,只是在构造k叉哈夫曼树时需要先进行一些调整。解决这个问题的办法是假设已经有了一棵哈夫曼树(且为一棵满k叉树),则可以计算出其叶节点数目为(k-1)nk+1,式子中的nk表示子节点数目为k的节点数目(也就是上面的中间结点以及根节点)。

于是对给定的n个权值构造k叉哈夫曼树时,可以先考虑增加一些权值为0的叶子节点,使得叶子节点总数为(k-1)nk+1这种形式,然后再按照哈夫曼树的方法进行构造即可。

 

第二种不妨在算法的开始,我们就先取掉x个数,使得剩下的n-x个数正好能每次取k个取完,也就是利用第一种推导出的公式进行删除

if((N-k)%(k-1)==0) num=k;
else num=(N-k)%(k-1)+1;

其中N为叶子结点的数量

算法总的来说,就是两个指针从首扫到尾,时间复杂度不超过O(2*n),已经是非常优秀。

关于k叉哈夫曼树在ACM中的最常用的应用无疑就是求合并n个数最小的代价。


HDU 5884
4198: [Noi2015]荷马史诗

关于K叉哈夫曼树的模板如下:


int hafuman(int k)
{
    int ai,bi,blen;
    blen=0;
    ai=bi=0;
    int cost=0;
    bool first=true;
    while(N-ai+blen-bi>1){
        int num=0;
        if(first){
            if((N-k)%(k-1)==0){
                num=k;
            }else{
                num=(N-k)%(k-1)+1;
            }
            first=false;
        }else{
            num=k;
        }
        int sum=0;
        while(num--){
            if(ai==N){
                sum+=b[bi];
                bi++;
            }else if(bi==blen){
                sum+=a[ai];
                ai++;
            }else if(a[ai]<b[bi]){
                sum+=a[ai];
                ai++;
            }else{
                sum+=b[bi];
                bi++;
            }
        }
        cost+=sum;
        b[blen++]=sum;
    }
    return cost;
}

 

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值