K叉哈夫曼树构造方法 O(N)

带权路径:是树中所有的叶结点的权值乘上其到根结点的路径长度。

哈夫曼树就是带权路径最小的树。


有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个数可取了,所以不妨在算法的开始,我们就先取掉x个数,使得剩下的n-x个数正好能每次取k个取完。

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


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


HDU 5884
4198: [Noi2015]荷马史诗

给一个模版

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;
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值