这次同样是动态规划的问题,其核心思想与此前的viterbi算法一样:基于局部的子最优寻找全局最优,每走一步都是取决于前一步的最优解。因而,动态规划方法的计算复杂度也会大大降低。
这次的硬币问题是一个经典的动态规划问题模型:现在拥有面值为3元、6元、7元的硬币若干;问:如何用最少数量的硬币凑出18元?
分析:在考虑动态规划之前,可能我们会使用贪心算法来实现,但显然使用贪心算法7+7+3=17,并不能保证找出最优解。(贪心算法每次会选取可能的最大面值来实现硬币总数最少)并且在该问题中,最优解显然存在:6+6+6=18(最少3个硬币)。
关键点:找到状态转换方程:”d(i)=d(j)+1(j为i的前一个阶段)”,并理解为何是“+1”?
现在,我们开始考虑动态规划(DP)的方法。我们设d(i)为凑够i元所需的最少硬币数目,显然d(0)=0,0元不用凑;d(1)=99999,硬币的最小面值为3元,1元显然凑不了(与之前dijkstra算法一样用99999来表示不能实现的情况);同理,d(2)=99999;当要凑够3元的时候,只需要在凑够0元的基础上再凑一个3元硬币就OK了,所以d(3)=d(3-3)+1。然后接下来:d(4)=99999;d(5)=99999;到凑6元的时候我们就发现了问题:可以用2个3元或者1个6元凑够6元。因此我们需要比较min{d(6-3)+1,d(6)=d(6-6)+1};注意,这里”d(6-3)+1″的意义在于:在凑够3元的情况下再凑3元。(即之前所说的:基于此前的状态再走一步)。但是,这里”d(6-3)+1=2>d(6-6)+1=1″,最优解应是:d(6)=d(6-6)+1=1;接下来,d(7)=d(7-7)+1=1;好了,到这里,这个阶段就结束了,我们分别对d(1)~d(7)分别找到了最优解(包括无解的情况)。
好,接下去一个阶段。d(8)=99999;到凑9元了!要凑够9元,我们可以从前一个步骤的最优解解集中选一个,然后再走一步!d(9)=min{d(9-3)+1,d(9-6)+1}=2。显然,在计算d(6)时,我们就不需要再计算要选择1个6元还是2个3元了!因为前个步骤已经计算并保存了凑够6元的最优解d(6)!这就是动态规划算法的核心所在,并能降低算法复杂度的原因!
好了,不说废话了,直接上代码(c++)!
方法一:
/*递归做法*/
#include<iostream>
using namespace std;
int coins[3] = {3,6,7};
int d[19] ; //存放0到18元组成的硬币数
int min(int a,int b)
{
return (a<=b)? a:b;
}
void min_coins(int i,int num) //从i元开始凑够num元
{
if(i == 0)
{
d[i] = 0;
min_coins(1,num);
return;
}
else
{
int MIN = 9999;
for(int j=0;j<3;j++)
{
if(i>=coins[j])
{
MIN = min(d[i-coins[j]]+1,MIN);
}
}
d[i] = MIN;
if(i == num)return;
else
min_coins(i+1,num);
}
}
int main()
{
min_coins(0,18); //表示要凑齐18元的硬币
for(int i=0;i<19;i++)
{
cout<<"凑齐"<<i<<"元,至少需要"<<d[i]<<"枚硬币"<<endl;
}
return 0;
}
方法二:
/*循环做法*/
#include<iostream>
using namespace std;
int coins[3] = {3,6,7}; //硬币面值
int d[19]; //存放0到18元组成的硬币数
//比较大小
int min(int a, int b){
return (a<=b) ? a:b;
};
//初始化
void initD(){
for(int i=0; i<19; i++){
d[i] = 99999;
}
};
//主要功能实现
void min_coins(){
for(int i=0; i<19; i++){
if(i==0){
d[0]=0;
continue;
}
int MIN = 99999; //重设MIN
for(int j=0; j<3; j++){
if(i>=coins[j]){
MIN = min(d[i-coins[j]]+1,MIN); //比较大小,寻找最佳
}
else break; //第一个不合条件就退出,优化算法
d[i] = MIN;
}
}
}
void main(){
initD();
min_coins();
for(int k=0; k<19; k++){
cout<<"凑齐"<<k<<"元,至少需要"<<d[k]<<"枚硬币"<<endl; //输出结果
}
}