01背包

↘锁芯ラ 提交于 2020-01-27 08:05:57

问题描述

已知:有一个容量为V的背包和N件物品,第i件物品的重量是weight[i],价值是cost[i]:在不超过背包容量的情况下,最多能获得多少价值。

01背包的特点:每种物品只有一件,可以选择放或者不放

1子问题:f[i][v] 表示前i件物品放到一个容量为v的背包中可以获得最大价值  
2.状态转移方程:f[i][v] = max(f[i - 1][v],f[i - 1][v - weight[i]] + cost[i])  

1) 如果第i件物品不放入背包中,那么问题就转换为:将前i - 1件物品放到容量为v的背包中,带来的收益f[i - 1][v]

2) 如果第i件物品能放入背包中,那么问题就转换为:将前i - 1件物品放到容量为v - weight[i]的背包中,带来的收益f[i - 1][v - weight[i]] + cost[i]

代码【01背包二维数组实现】

#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;

int N,V; //物品个数  ,背包最大容量
int weight[100],value[100];//物品重量 ,物品价值

int f[100][100] = {{0}};

/*
目标:在不超过背包容量的情况下,最多能获得多少价值

子问题状态:f[i][j]:表示前i件物品放入容量为j的背包得到的最大价值

状态转移方程:f[i][j] = max{f[i - 1][j],f[i - 1][j - weight[i]] + value[i]}

初始化:f数组全设置为0
*/
int Knapsack()
{
    //初始化
    memset(f,0,sizeof(f));
    freopen("input.txt","r",stdin);
    cin>>N>>V;
    for(int i=1;i<=N;i++)
        cin>>weight[i];
    for(int i=1;i<=N;i++)
        cin>>value[i];
    //递推
    for (int i = 1;i <= N;i++) //枚举物品
    {
        for (int j = 0;j <= V;j++) //枚举背包容量
        {
            if(j < weight[i])
                f[i][j] = f[i - 1][j];
            else
                 f[i][j] = max(f[i - 1][j],f[i - 1][j - weight[i]] + value[i]);
        }
    }
    return f[N][V];
}

int main()
{
    cout<<Knapsack()<<endl;
    return 1;
}

滚动优化:上述的方法,我们使用二维数组 f[i][v] 保存中间状态,这里我们可以使用一维数组f[v]保存中间状态就能得到结果

i次循环后,f[v]中存储的是前i个物体放到容量v时的最大价值只不过针对不同的if[v]一直在重复使用,所以,也会出现第i次循环可能会覆盖第i - 1次循环的结果。

1.	for i=1..N //枚举物品  
2.	    for v=V..0 //枚举容量,从大到小  
3.	        f[v]=max{f[v],f[v-weight[i]] + cost[i]};  

逆序枚举容量的原因:

注意一点,我们是由第 i - 1 次循环的两个状态(f[v],f[v - weight[i]])推出 i 个状态的,而且 v  > v - weight[i],则对于第i次循环,背包容量只有当V..0循环时,才会先处理背包容量为v的状况,后处理背包容量为 v-weight[i] 的情况。

具体来说,由于,在执行v时,还没执行到v - weight[i]的,因此,f[v - weight[i]]保存的还是第i - 1次循环的结果。即在执行第i次循环 且 背包容量为v时,此时的f[v]存储的是 f[i - 1][v] ,此时f[v-weight[i]]存储的是f[i - 1][v-weight[i]]。

相反,如果在执行第 i 次循环时,背包容量按照0..V的顺序遍历一遍,来检测第 i 件物品是否能放。此时在执行第i次循环 且 背包容量为v时,此时的f[v]存储的是 f[i - 1][v] ,但是,此时f[v-weight[i]]存储的是f[i][v-weight[i]]。

因为,v  > v - weight[i],第i次循环中,执行背包容量为v时,容量为v - weight[i]的背包已经计算过,即f[v - weight[i]]中存储的是f[i][v - weight[i]]。即,对于01背包,按照增序枚举背包容量是不对的。

代码【01背包一维数组实现】

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

int N,V; //物品个数  ,背包最大容量
int weight[100],value[100];//物品重量 ,物品价值
int f[100]={0};

int Knapsack()
{
    memset(f,0,sizeof(f));
    for (int i = 1;i <= N;i++) //枚举物品
    {
        for (int j = V;j >= weight[i];j--) //枚举背包容量,防越界,j下限为 weight[i]
        {
            f[j] = max(f[j],f[j - weight[i]] + value[i]);
        }
    }
    return f[V];
}

int main()
{
    freopen("input.txt","r",stdin);
    cin>>N>>V;
    for(int i=1;i<=N;i++)
        cin>>weight[i];
    for(int i=1;i<=N;i++)
        cin>>value[i];
    cout<<Knapsack()<<endl;
    return 1;
}

增序枚举背包容量会达到的效果:它会重复的装入某个物品,而且尽可能多的,使价值最大。

而逆序枚举背包容量:背包中的物品至多装一次,使价值最大。

以上是在不超过背包容量的情况下讨论的,在恰好装满背包的情况,需要注意初始化细节问题::

二维:除了f[i][0] = 0(第一列)外,其他全为负无穷。

一维:除了f[0] = 0,其他全为负无穷。

代码【二维】

#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;

const int MinNum = 0x80000000;//负无穷

int N,V; //物品个数  ,背包最大容量
int weight[100],value[100];//物品重量 ,物品价值

int f[100][100];
/*
目标:在恰好装满背包的情况下,最多能获得多少价值

初始化:除了f[i][0] = 0(第一列)外,其他全为负无穷
*/
int Knapsack()
{
    //初始化
    for (int i = 0;i <= N;i++)
        for (int j = 0;j <= V;j++)
            f[i][j] = MinNum;

    for (int i = 0;i <= N;i++)
        f[i][0] = 0;

    for (int i = 1;i <= N;i++) //枚举物品
    {
        for (int j = 1;j <= V;j++) //枚举背包容量
        {
            if(j < weight[i])
                f[i][j] = f[i - 1][j];
            else
                 f[i][j] = max(f[i - 1][j],f[i - 1][j - weight[i]] + value[i]);

        }
    }
    return f[N][V];
}

int main()
{
    freopen("input.txt","r",stdin);
    cin>>N>>V;
    for(int i=1;i<=N;i++)
        cin>>weight[i];
    for(int i=1;i<=N;i++)
        cin>>value[i];
    cout<<Knapsack()<<endl;//输出25
    return 1;
}

代码【一维】

#include <iostream>
#include <cstdio>
using namespace std;

const int MinNum = 0x80000000;//int最小的数

int N,V; //物品个数  ,背包最大容量
int weight[100],value[100];//物品重量 ,物品价值
int f[100];
/*
目标:在恰好装满背包容量的情况下,最多能获得多少价值
初始化:除了f[0] = 0,其他全为负无穷
*/
int Knapsack()
{
    for (int i = 0;i <= V;i++)
        f[i] = MinNum;
    f[0]=0;
    //递推
    for (int i = 1;i <= N;i++) //枚举物品
    {
        for (int j = V;j >= weight[i];j--) //枚举背包容量,防越界,j下限为 weight[i]
        {
            f[j] = max(f[j],f[j - weight[i]] + value[i]);
        }
    }
    return f[V];
}

int main()
{
     freopen("input.txt","r",stdin);
    cin>>N>>V;
    for(int i=1;i<=N;i++)
        cin>>weight[i];
    for(int i=1;i<=N;i++)
        cin>>value[i];
    cout<<Knapsack()<<endl;//输出25
    return 1;
}

input.txt测试案例:

3 5
3 2 2
5 10 20

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!