您的位置:首页 > 其它

动态规划之背包问题初研究

2015-09-27 11:29 337 查看
//最近听从明神的建议,看了一点动态规划方面的内容,明显感觉智商不够用,所以我会尽可能将此段内容写成博客,并尽可能详细~~

01背包问题

有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。

因为每个物品只有一件,只有两种状态,放与不放,故称为0-1背包问题.

0-1背包问题的状态方程为
f[i][j]=max{f[i-1][j],f[i-1][j-w[i]]+a[i]};


其中f[i][j]是前i个物品放入容量j的背包的最大价值,而如果不放入第i个物品,则是前i-1个物品占用j个背包的最大价值,而如果放入第i个物品则是前i-1个物品占用j-w[i](总容量-第i个物品的空间)+第i个物品的价值.

#include <stdio.h>
#include <memory.h>
int max(int a,int b)
{
    return a>b?a:b;
}
 int main()
 {
      int a[1000];
      int f[100][100];
      int w[1000];
      int i,j,n,m;
      printf("请输入物品的数量\n");
      scanf("%d",&n);
      printf("请依次输入物品的价值\n");
      for (i=1;i<=n;i++)
      scanf("%d",&a[i]);
      printf("请输入背包的最大容量\n");
      scanf("%d",&m);
      printf("请依次输入物品的所占空间\n");
      for (i=1;i<=n;i++)
      scanf("%d",&w[i]); //此时的目的是将n个物品放入空间为m的背包里,第i个背包价值为a[i],所占空间为w[i],接下来求最大价值
      memset(f,0,sizeof(f));
        for(i=1;i<=n;i++)
        for (j=0;j<=m;j++)
            if (j>=w[i])//这里与状态方程稍有出入,因为如果<span style="font-family:宋体;">现在我提供的空间的都不够放入i物品,那么</span>肯定不放入,直接就是f[i-1][j](<span style="font-family:宋体;">主要是怕j-w[i]下标越界)</span>
                f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+a[i]);
            else
                f[i][j]=f[i-1][j];
                printf("最大价值为:%d",f
[m]);
                return 0;
      }
我觉得这个式子如何计算也有必要讲解一下:

假设背包最大容量为10

1号物品价值为2,空间为4

2号物品价值为3,空间为5

3号物品价值为4,空间为6

那么当考虑1号背包时,空间为0,1,2,3,都装不下,直到4开始放入背包,f[1][4]=2;以后所有的j,f[i][j]都会取2,因为所有的f[0][j]都是0。当考虑2号物品时,f[2][0]=0,f[2][1]=0,f[2][2],f[2][3]都不变,直到f[2][4]=2,而f[2][5]时变为3(其实我们想想,假设背包容量就是5,那么自然最大价值就是2号物品的价值),之后直到f[2][9]时f[2][9]=max(f[1][9],f[1][4]+a[2])则修改为5,接下去考虑3号物品,f[3][0]=0,f[3][1]=0,f[3][2]=0,f[3][3]=0,f[3][4]=2,f[3][5]=3,f[3][6]=4,f[3][7]=4,f[3][8]=4,f[3][9]=5,f[3][10]=max(f[2][10],f[2][4]+4},即f[3][10]=6,成功了~~

如果参看网上的许多代码,会发现他们是从0开始循环的,这样f[i-1][j]就可能会为负数,但是一般f[-1][j]也为0,所以并没有问题。

用一维数组来表达很重要,因为f这个二维数组是和n,m的大小直接挂钩的,至少为两者的平方,所以空间占据很大,有些题目会控制空间,所以需要理解用一维数组来解决01背包问题~~

一维数组的伪代码如下:

for i=1..N

for
v=V..0

f[v]=max{f[v],f[v-c[i]]+w[i]};

注意一定内层循环一定要逆序。我们知道f[v]是由两个状态得来的,f[i-1][v]和f[i-1][v-c[i]],使用一维数组时,当第i次循环之前时,f[v]实际上就是f[i-1][v],所以只需考虑f[v]和f[v-c[i]]+w[i]哪个更大,至于逆序是因为假设背包容量为5,而有一个第2号物品价值特别大,则考虑4号物品时,f[v]=max{f[v],f[v-c[i]+w[i]}的结果是再将2号装了一次,这就会导致2号物品被装了两次,而每个物品只有一个,所以不符合题意。但是这却是完全背包的解法,因为完全背包是允许一个物品多次入包的。

练习题:http://poj.org/problem?id=3624

此题一开始是用二维数组做的,不幸空间溢出~~

#include<stdio.h>
#include <string.h>
int max(int a,int b)
{
    return a>b?a:b;
}
 int main()
 {
   int p[3403][3403];
   int w[4005];//重量
   int d[4005];//价值
   int n,m,i,j;
   scanf("%d%d",&n,&m);
   for (i=1;i<=n;i++)
   scanf("%d%d",&w[i],&d[i]);
   memset(p,0,sizeof(p));
   for (i=1;i<=n;i++)//物品从1号到n号
    for (j=0;j<=m;j++)//最大重量从0到m
     if (j>=w[i])//如果空间够放w[i]
    p[i][j]=max(p[i-1][j],p[i-1][j-w[i]]+d[i]);
   else
    p[i][j]=p[i-1][j];
    printf("%d\n",p
[m]);
    return 0;
 }

改用一维数组立马
AC
#include <stdio.h>
#include <string.h>
int max(int a,int b)
{
    return a>b?a:b;
}
 int main()
 {
     int f[14000];
     int i,j,n,m;
     int w[4005],d[4005];
     scanf("%d%d",&n,&m);
     for (i=1;i<=n;i++)
        scanf("%d%d",&w[i],&d[i]);
        memset(f,0,sizeof(f));
     for (i=1;i<=n;i++)
        for (j=m;j>=w[i];j--)
         f[j]=max(f[j],f[j-w[i]]+d[i]);
     printf("%d\n",f[m]);
     return 0;
}


接下来是完全背包问题,即每件物品可以取出任意件数

假设取出的件数为k

则他的状态转移方程为f[i][v]=max{f[i-1][v-k*c[i]+k*w[i]};(0<=k*c[i]<=v)

最好的方法是用一维数组

先看伪代码:

for i=1..N

for
v=0..V

f[v]=max{f[v],f[v-cost]+weight}

刚才介绍说如果内层循环正序,那么就可能出现一个物品被多次取出的问题,所以被排除,但是完全背包问题只要空间足够就可以不停地使用一个物品,比如2号物品重量为2,背包容量为10,只要2号背包的价值足够大,完全可能出现2号物品多次入包的情况.

练习题:http://poj.org/problem?id=1384

#include <stdio.h>//此题代码思路基本如上所述,
#include <string.h>
int min(int a,int b)
{
    return a<b?a:b;
}
 int main()
 {
     int T,e,f,i,j,n,k,empty,full;
     int w[600];//重量
     int v[600];//价值
     int p[10100];
     scanf("%d",&T);
     while(T--)
     {
        memset(v,0,sizeof(v));
        memset(w,0,sizeof(w));
        scanf("%d%d",&empty,&full);
        full=full-empty;
        p[0]=0;
        for (i=1;i<=full;i++)
        p[i]=10000000;
        scanf("%d",&n);
        for (k=1;k<=n;k++)
        {
            scanf("%d%d",&v[k],&w[k]);//价值数量
        }
        for (i=1;i<=n;i++)
            for (j=w[i];j<=full;j++)
               p[j]=min(p[j],p[j-w[i]]+v[i]);
            if (p[full]==10000000)
                printf("This is impossible.\n");
                else
            printf("The minimum amount of money in the piggy-bank is %d.\n",p[full]);
     }
            return 0;

     }


未完待续.....


                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: