哈弗曼树

http://poj.org/problem?id=3253

一根木棍,要切出相应长度的几段木条,每次切需要的费用是当前木棍的长度,问怎样切才能使费用最少?

比如:长度为13的木棍,要切出长度分别为 2, 3, 4, 4 的木条,最省钱的方法是先切成 5, 8 两段,然后 5 切成 2,3 两段,8 切成 4, 4 两段,这样总费用为 13 + 5 + 8 = 26

思路:考虑切过程的逆过程,即有长度分别为 2, 3, 4, 4 的木条,怎样拼成长度为 13 的木棍费用最省,如果将这些长度表示为哈弗曼树的叶子节点,我们需要最小化的费用其实就是 sum ( 叶子节点取值 *   根节点到其长度  ) ,而这正是哈弗曼树最小化的


附哈夫曼树正确性的证明:(转自http://mindhacks.cn/2011/07/10/the-importance-of-knowing-why-part3/

我们首先看一看《Algorithms》上是怎么讲的:

首先它给出了一棵编码树的cost function:

cost of tree = Σ freq(i) * depth(i)

这个cost function很直白,就是把编码的定义复述了一遍。但是接下来就说了:

There is another way to write this cost function that is very helpful. Although we are only given frequencies for the leaves, we can define the frequency of any internal node to be the sum of the frequencies of its descendant leaves; this is, after all, the number of times the internal node is visited during encoding or decoding…

接着就按照这个思路把cost function转换了一下:

The cost of a tree is the sum of the frequencies of all leaves and internal nodes, except the root.

然后就开始得出算法逻辑了:

The first formulation of the cost function tells us that thetwo symbols with the smallest frequencies must be at the bottom of the optimal tree, as children of the lowest internal node (this internal node has two children since the tree is full). Otherwise, swapping these two symbols with whatever is lowest in the tree would improve the encoding.

This suggests that we start constructing the tree greedily: find the two symbols with the smallest frequencies, say i and j, and make them children of a new node, which then has frequency fi + fj. To keep the notation simple, let’s just assume these are f1 and f2. By the second formulation of the cost function, any tree in which f1 and f2 are sibling-leaves has cost f1 + f2 plus the cost for a tree with n – 1 leaves of frequencies (f1 + f2), f3, f4, .., fn.The latter problem is just a smaller version of the one we started with.


#pragma warning (disable:4786)  
#include<iostream>
#include<algorithm>
#include<math.h>
using namespace std;
#define MAX 20000
int heap[MAX + 10];   //最小堆  
int heap_num;         //最小堆的 size + 1,初始设为1
//自顶向下调整堆
void heap_down(){
	int num  = 1;
	int data = heap[num];
	while(num*2<heap_num){
		int left=num*2;
		int right=num*2+1;
		if( data > heap[left] || ( right < heap_num && data > heap[right] ) ){
			int min=left;
			if( right < heap_num && heap[min] > heap[right] ){
				min = right;
			}
			int temp = heap[num];
			heap[num] = heap[min];
			heap[min] = temp;
			num = min;
		}
        else break;
	}
}
//自底向上调整堆
void heap_up(){
	int num = heap_num - 1;
	int data = heap[num];
	while(num>1){
		if(heap[num/2]>data){
			int temp=heap[num];
			heap[num]=heap[num/2];
			heap[num/2]=temp;
			num=num/2;
		}
		else break;
	}
}
//堆的插入
void heap_insert(int data){
	if(heap_num==1){
		heap[1] = data;
		heap_num ++;
		return;
	}
	heap[heap_num] = data;
	heap_num ++; 
	heap_up();   
}
//取最小元素并删除
int get_min(){
	if( heap_num == 1 )
		return -1;
	int min = heap[1];
	heap[1] = heap[heap_num - 1];
	heap_num --;
	heap_down();
	return min;
	
}
	
int main(){
	int n,i,a;
	long long cost = 0;
	scanf("%d",&n);
	heap_num = 1;

	for( i = 0; i < n; i ++ ){
		scanf("%d",&a);;
		heap_insert( a );
	}

	while( heap_num > 2 ){
		//每次取最小的两个元素并删除它们
		int x = get_min();
		int y = get_min();

        //将它们的和插入最小堆
		int z = x + y;
		heap_insert( z );

		//将它们的和计入费用
		cost = cost + (long long) z;
		
	}
	printf("%lld\n", cost);
		
	return 0;
}


 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值