浅谈动态规划(个人理解)

  动态规划,是常规的解决问题的一种方法,能解决的问题具有子问题的性质,即将大问题化成小问题进行分析解决,动态规划最重要的无非两点:状态和状态转移方程。所谓状态,指的是动态规划在化成每一个小问题时的状态,而状态转移方程,则是动态规划的关键:即将大问题化成小问题的方程:每个小问题的最优解都可以由这个方程得到,而大问题的最优解则是建立在小问题的最优解的基础上,下面我将通过一道经典题目来方便理解动态规划。

经典动规问题之0-1背包问题:

   小明在旅行时偶然碰见一些钻石(假设有n个),而他只有一个背包,这个背包具有最大能承受的重量W,这n个钻石都有价值v和重量w,显而易见,钻石不能分解,所以小明对每个钻石只能选择带或不带每个钻石,请问:小明最大能带的钻石的价值是多大?

分析:很明显,这是一个动态规划问题:因为它能分解成小问题最优解--每个钻石带或不带,因此,我们只需要比较带了当前钻石以后的价值和不带当前钻石的价值哪个大,再考虑带还是不带当前钻石:没错,这就是我们此题的状态。假设我们用m[i][j]来保存有i个物品,最大承受重量为j时的最大价值,w[i],v[i]表示第i个物品的重量和价值那么我们的1状态转移方程又上面得:m[i][j] = max(m[i - 1][j],m[i - 1][j - w[i]] + v[i] ),分别表示不带当前物品的的价值和带上当前物品的价值,两者取大则为问题最优解,而最有优解也可以化为这样的方程,从而求得最优解的最优解,则得到的一定是最优解,接下来再上代码,就很好理解了:

/*0-1背包问题*/
#include <stdio.h>
int m[1000],v[1000],sum[1000][1000];//m[i]为每一个物品的重量,v[i]为每一个物品的价值,sum[i]用来存放背包可存放的最大价值

int max(int a,int b)
{
   return (a > b ? a : b);
}
int main(void)
{
   int i,j,M,n;//M为背包能装的最大重量,n为可选择物品个数
   while (scanf ("%d%d",&n,&M) != EOF)//输入可选择的物品个数
   {
      for (i = 0;i < n;i++)
      {
         scanf ("%d%d",&m[i],&v[i]);//输入每个物品的重量和价值
      }
      for (i = 0;i <= n;i++)
      {
         for (j = 0;j <= M;j++)
         {
            sum[i][j] = i == 0 ? 0 : sum[i - 1][j];//先将大问题表示为不带当前物品时的价值
            if(i > 0 && j >= m[i - 1])
            {
               sum[i][j] = max(sum[i][j],sum[i - 1][j - m[i - 1]] + v[i - 1]);//再将此时的价值和带上当前物品时的价值进行比较,两者取大
            }
         }
      }
      printf ("%d\n",sum[n][M]);//输出有n个物品,最大承受重量为M时的最大价值
   }

   return 0;
}/*未完待续*/

2017.7.6更新

结接下来我们来看看另外一道有关动态规划的经典问题:硬币找零问题

问题描述:

给予不同面值的硬币若干种种(每种硬币个数无限多),用若干种硬币组合为某种面额的钱,使硬币的的个数最少。

在现实生活中,我们往往使用的是贪心算法,比如找零时需要13元,我们先找10元,再找2元,再找1元。这是因为现实生活中的硬币(纸币)种类特殊。如果我们的零钱可用的有1、2、5、9、10。我们找零18元时,贪心算法的策略是:10+5+2+1,四种,但是明明可以用两个9元的啊。

所以可以使用动态规划,找零18元时,我们首先找18-1=17,18-2=15,18-5,=13,18-9,=9,18-10=8;再找17-1……。这样递归解决子问题,其实,这种递归解决问题也可以改为递推解决。很明显,这道题的状态转移方程为:num[i] = min(num[i - a[0] + 1,num[i - a[1]] + 1 .........),其中,num[i]表示i元钱所需要找零的最少纸币数目,a[i]表示第i中纸币的面值。这样从后分析,从前递推,代码很容易可以得出。

代码:

#include <stdio.h>
#include <string.h>
int coin[10],coinnum[200];//coin代表可用来找零的硬币的面值,coinnum代表最多的找零数
int Min(int a,int b)
{
return a > b ? a : b;
}
int main(void)
{
int i,j,n,S,t,k;//S代表需要找零的钱币,n代表用来找零的硬币的种数
while (scanf ("%d%d",&n,&S))
{
coinnum[0] = 0;
for (i = 0 ;i < n;i++)
{
scanf ("%d",&coin[i]);//输入用来找零的钱的面值
}
for (i = 0;i < n - 1;i++)
{
k = i;
for (j = i + 1;j < n;j++)
{
if(coin[j] > coin[k])
{
k = j;
}
}
t = coin[k];
coin[k] = coin[i];
coin[i] = t;
}
for(i = 0;i < n - 1;i++)
{
coinnum[i] = 0;
}
for (i = coin[n - 1];i <= S;i++)
{
coinnum[i] = 0;
for (j = 0;j < n;j++)
{
if(i >= coin[j])
{
coinnum[i] = Min(coinnum[i],coinnum[i - coin[j]] + 1);
printf("%d %d\n",i,coinnum[i]);
}
}
}
printf ("%d\n",coinnum[S]);
}

return 0;
}

 

转载于:https://www.cnblogs.com/Tang123456/p/6758162.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值