zoukankan      html  css  js  c++  java
  • 01背包

    问题描述

    已知:有一个容量为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

  • 相关阅读:
    2016-2017-1 《信息安全系统设计基础》第八周课程总结
    2016-2017-1 《信息安全系统设计基础》第七周课程总结
    2016-2017-1 《信息安全系统设计基础》第六周课程总结
    利用视频会议实训系统进行演示的教程
    别出心裁的Linux系统调用学习法
    基于VirtualBox安装Ubuntu图文教程
    做中学之Vim实践教程
    三百六十五分之一:永与永字八法
    代码驱动的程序设计学习
    2016-2017-1 《信息安全系统设计基础》加扣分项目
  • 原文地址:https://www.cnblogs.com/qie-wei/p/10160166.html
Copyright © 2011-2022 走看看