背包问题之01背包

问题描述
已知n个物品和一个背包容量为C,物品i(i=1,2,3,......)的容量为c[i]  价值w[i]。
物品i能够装入,也能够不装入,可是不能够拆分。如何设计装包使得装包
总效益最大。

动态规划逆推求解
设dp[i,j]为背包容量j,可取物品的范围为i,i+1,i+2,......n的最大效益值。
即这是从后往前做为递推的方向。也就是思考怎么从i+1这个状态转移到i的状态
无非是放入和不放两种状况。
这里:
当  j<c[i]的时候  物品没法放入。最大效益值为dp[i+1,j]相同。
当 j>=c[i]的时候  会出现两个选择:
    (1)不放入物品i  最大效益值为dp[i+1,j]
    (2)放入物品i  这个时候的最大效益是 dp[i+1,j-c[i]]+w[i]
这里咱们指望的最大效益是这二者中的最大值。

所以这个递推方程为  dp[i,j]=max{  dp[i+1,j]  ,dp[i+1,j-c[i]]+w[i]  }
这个方程全面一点就是

这里既能够安装上面的思路推出,也能够  由dp[i,j]=max{  dp[i+1,j]  ,dp[i+1,j-c[i]]+w[i]  }
想到 j>c[i]才能够保证结果是大于0 从而想到须要比较 j和c[i]的大小。从而得出完整的递推关系式。
# include <stdio.h >
# include <string.h >
int main()
{
  int n,c,p[ 50],w[ 50],m[ 50][ 500],dp[ 500];

  //参数输入
  printf( "请物品的个数和背包的总重量:");
  scanf( "%d%d", &n, &c);
  for( int i = 1;i < =n;i ++)
  {
    printf( "输入w%d p%d:",i,i);
    scanf( "%d%d", &w[i], &p[i]);
  }

  //递推
  //初始化的操做
  //注:如数组没有初始化 则其值是随机的
  for( int j = 0;j < =c;j ++)
  {
    if(j > =w[n]) m[n][j] =p[n];
    else        m[n][j] = 0;
  }

  //使用递推关系
  for( int i =n - 1;i > = 1;i --)
    for( int j = 0;j < =c;j ++)
    {
      if(j > =w[i] &&m[i + 1][j -w[i]] +p[i] >m[i + 1][j])
        m[i][j] =m[i + 1][j -w[i]] +p[i];
      else
        m[i][j] =m[i + 1][j];
    }
    printf( "最大值:%d\n",m[1][c]);
    return 0;



动态规划顺推求解
设dp[i,j]为背包容量为j,可选的物品为1,2,3,....i的时候的最大效益。
从前日后递推,有i-1状态转移到i状态。
# include <stdio.h >
# include <string.h >


int main()
{
  int n,c,p[ 50],w[ 50],m[ 50][ 500],dp[ 500];

  //参数输入
  printf( "请物品的个数和背包的总重量:");
  scanf( "%d%d", &n, &c);
  for( int i = 1;i < =n;i ++)
  {
    printf( "输入w%d p%d:",i,i);
    scanf( "%d%d", &w[i], &p[i]);
  }
  
    //顺推
  //初始化
  for( int j = 0;j < =c;j ++)
  {
    if(j > =w[ 1]) m[ 1][j] =p[ 1];
    else        m[ 1][j] = 0;
  }
  //利用递推关系
  for( int i = 2;i < =n;i ++)
    for ( int j = 0;j < =c;j ++)
    {
      if(j > =w[i] &&m[i - 1][j] <m[i - 1][j -w[i]] +p[i])
        m[i][j] =m[i - 1][j -w[i]] +p[i];
      else
        m[i][j] =m[i - 1][j];
    }
  

  pritf( "%d",m[n][c])
  return 0;
}



对于初始化,能够直接所有赋值为0便可。第二次循环从w[i]开始
# include <cstdio >
# include <cstring >
# include <algorithm >

using namespace std;

int n,c,p[ 50],w[ 50],m[ 50][ 500],dp[ 500];

int main()
{
  //参数输入
  printf( "请物品的个数和背包的总重量:");
  scanf( "%d%d", &n, &c);
  for( int i = 1;i < =n;i ++)
  {
    printf( "输入w%d p%d:",i,i);
    scanf( "%d%d", &w[i], &p[i]);
  }

  memset(m, 0, sizeof(m));

  for( int i = 1;i < =n;i ++)
    for( int j =c;j > =w[i];j --)
       m[i][j] =max(m[i - 1][j],m[i - 1][j -w[i]] +p[i]);

  printf( "%d",m[n][c]);

  return 0;
}


进一步的思考
上面的思惟实际是背包的从前日后 以及从后往前的比较。这里进一步想想
程序实际是两个for循环,咱们上面实际都是对i 从前日后和从后往前。
能不能这里对j也有两种遍历呢?
在这篇博客里面引入了一个问题。就是将二位数组下降为一维。
再降维的时候 咱们只能将第二个循环从大到小进行遍历。缘由是若是从小往大遍历就会覆盖。

一维的程序很容易写成:
dp[j] =max { dp[j] , dp[j-c[i]+w[i]] }
参考《背包九讲》因而伪代码是:
for  i = 1......N
    for  v =V...... 0
          dp[v] =max{dp[v],dp[v -c[i]] +w[i]}
其实这个代码仍是有点抽象的,
在真正编程的时候应该这么去写:

for  i = 1......N
      for  v =V...... 0
          if(v > =c[i])  dp[v] =max(dp[v],dp[v -c[i]] +w[i]);
          else           dp[v] =dp[v];
       

这个时候咱们很清楚的就能够看到了 在v<c[i]的时候 实际上dp[v]是没有发生变化的。
所以咱们能够减小这一步的操做。将v = V......c[i]进一步加一优化。
for i = 1.........N
    for  v =V......c[i]
          dp[v] =dp[v] >dp[v -c[i]] +w[i] ?dp[v] :dp[v -c[i]] +w[i];


同时关于初始化的细节,《九讲》里面也说得很不错。
若是是背包必须装满即最后的最大值为V。那么初始化 dp[0]=0, dp[1.....V]=-INF
若是能够不装满 那么只用memset(dp,0,sizeof(dp))所有初始化为0.






















相关文章
相关标签/搜索