Dynamic Programming 学习笔记(一)

我们经常会接触到一些算法的题目,而某一类的题目都是用Dynamic Programming(动态规划)来解决。

动态规划的概念

动态规划通常基于一个递推方程和一个(或者多个)初始状态。它将原问题分解为相似的子问题,在求解的过程中通过子问题的解求出原问题的解。使用DP通常可以得到多项式的时间复杂度。

动态规划的两个特征

1.     最优子结构性质

如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。

2.     子问题重叠性质

子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。

 

我们通过例子来学习动态规划。

题目:给定面值1,3和5的硬币,求使用最少硬币组成和为11的方案?

分析:这个题目的状态是‘和’i (i <= 11)。对于每一个硬币j Vj<=i (Vj 表示面值),查询一下构成i-Vj硬币总和所需要的最少硬币数目(我们之前已经计算过了)。不妨假设这个数字是m,如果m+1小于所求得硬币总和为i的最少硬币数量,我们更新它的状态。

由此可得状态转移公式:number[Si] =min (number[Si - V[j]])   (1<Si<=sum, 0<j<coinNumber)

式中,number[Si]表示和为Si的最少硬币数。V[j]表示硬币的面值。

代码如下:

void find_min_coin_sum1(int coinValue[], int coinNum, int sum)
{
	const int MAX = 100;
	int pmin[MAX] = {0};

	//Initialize to the max...
	for (int index = 1; index <= sum; ++index)
		pmin[index] = MAX;

	for (int sub_sum = 1; sub_sum < sum + 1; ++sub_sum)
		for (int coin_index = 0; coin_index < coinNum; ++coin_index)
			if (coinValue[coin_index] <= sub_sum && (pmin[sub_sum - coinValue[coin_index]] + 1 < pmin[sub_sum]))
				pmin[sub_sum] = pmin[sub_sum - coinValue[coin_index]] + 1;

	cout << "Dear, the sum combine is " << pmin[sum] << endl;
}

此外,如果如果我们记录下从前一个状态得到当前状态所需要的硬币,我们可以找出组成该硬币总和所需要的所有硬币。例如: 要组成硬币总和11,我们可以通过面值为1的硬币加上面值为10的硬币。为了得到10,我们需要得到5,而5需要从0,因此我们找到了所使用到的硬币:1,5和5。

代码如下:

void find_min_coin_sum2(int coinValue[], int coinNum, int sum)
{
	const int MAX = 100;
	vector<map<int, int> > vec(sum+1);
	int pmin[MAX] = {0};

	//Initialize to the max...
	for (int index = 1; index <= sum; ++index)
		pmin[index] = MAX;

	for (int sub_sum = 1; sub_sum < sum + 1; ++sub_sum)
		for (int coin_index = 0; coin_index < coinNum; ++coin_index)
			if (coinValue[coin_index] <= sub_sum && (pmin[sub_sum - coinValue[coin_index]] + 1 < pmin[sub_sum]))
			{
				pmin[sub_sum] = pmin[sub_sum - coinValue[coin_index]] + 1;
				map<int, int> coin_sum_map;
				coin_sum_map.insert( pair<int, int>(coinValue[coin_index], sub_sum - coinValue[coin_index]) );
				vec[sub_sum] = coin_sum_map; 
			}

	//output the coin value
	cout << "the min coin sum to " << sum << " is " ;
	do {
		map<int, int> coin_map = vec[sum];
		if (!coin_map.size())
			break;
		cout << coin_map.begin()->first << " ";
		sum = coin_map.begin()->second;
	} while( sum > 0);
}

现在让我们从一个不同的角度来看待这个问题。已知了硬币总和为0需要0枚硬币。现在我们试图在所有已经知道的结果中加上第一个,硬币(面值为1)。如果硬币总和t比起它的前一个状态需要的硬币数量更少,我们将更新它。对于第二个硬币,第三个硬币,和剩下的都是这样处理。例如,我们首先在硬币总和0上加上1得到硬币总和1. 标记S[1]=1。通过在硬币总和1上加上1,我们得到了硬币总和2,标记S[2]=2。然后对于第一个硬币依次下去。其余的硬币亦如此。这种方法类似素数的埃拉托斯特尼筛选法。

代码如下:

void find_min_coin_sum3(int coinValue[], int coinNum, int sum)
{
	const int MAX = 100;
	int pmin[MAX] = {0};

	//Initialize to the max...
	for (int index = 1; index <= sum; ++index)
		pmin[index] = MAX;

	for (int index = 0; index < coinNum; ++index)
	{
 		for (int sub_sum = 0; sub_sum <= sum - coinValue[index]; ++sub_sum)
		{
			if ( coinValue[index] + sub_sum <= sum &&
				 (pmin[sub_sum] + 1 <= pmin[coinValue[index] + sub_sum]) )
				pmin[coinValue[index] + sub_sum] = pmin[sub_sum] + 1;
		}
	}
	cout << "Dear, the sum combine is " << pmin[sum] << endl;
}

参考文献:

http://zh.wikipedia.org/wiki/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92

http://www.topcoder.com/tc?module=Static&d1=tutorials&d2=dynProg



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值