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

    参考:

    https://blog.csdn.net/yandaoqiusheng/article/details/84782655

    https://blog.csdn.net/qq_38410730/article/details/81667885

    1. 题目

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

    1. 01背包的模型以及解释

    Vi表示第 i 个物品的价值,Wi表示第 i 个物品的体积,定义V(i,j):当前背包容量 j,前 i 个物品最佳组合对应的价值

    递推关系式:

    包的容量比该商品体积小,装不下,此时的价值与前i-1个的价值是一样的

    V(i,j)=V(i-1,j);
    还有足够的容量可以装该商品,但装了也不一定达到当前最优价值,所以在装与不装之间选择最优的一个,即V(i,j)=max{V(i-1,j),V(i-1,j-w(i))+v(i)}
    3. 代码

    #include<iostream>
    using namespace std;
    #include <algorithm>
     
    int main()
    {
        //注意从1开始,0的位置都置零
    	int w[5] = { 0 , 2 , 3 , 4 , 5 };			//商品的体积2、3、4、5
    	int v[5] = { 0 , 3 , 4 , 5 , 6 };			//商品的价值3、4、5、6
    	int bagV = 8;					        //背包大小
    	int dp[5][9] = { { 0 } };			        //动态规划表
     
    	for (int i = 1; i <= 4; i++) {
    		for (int j = 1; j <= bagV; j++) {
    			if (j < w[i])//该物品太大放不进去
    				dp[i][j] = dp[i - 1][j];
    			else
    				dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);
    		}
    	}
     
    	//动态规划表的输出
    	for (int i = 0; i < 5; i++) {
    		for (int j = 0; j < 9; j++) {
    			cout << dp[i][j] << ' ';
    		}
    		cout << endl;
    	}
     
    	return 0;
    }
    

    结果即dp[4][8]就是最后的答案:能拥有的最多的价值

    1. 最优解回溯
      通过上面的方法可以求出背包问题的最优解,但还不知道这个最优解由哪些商品组成,故要根据最优解回溯找出解的组成,根据填表的原理可以有如下的寻解方式:

    V(i,j)=V(i-1,j)时,说明没有选择第i 个商品,则回到V(i-1,j)
    V(i,j)=V(i-1,j-w(i))+v(i)时,说明装了第i个商品,该商品是最优解组成的一部分,随后我们得回到装该商品之前,即回到V(i-1,j-w(i))
    一直遍历到i=0结束为止,所有解的组成都会找到。

    1. 代码
    #include<iostream>
    using namespace std;
    #include <algorithm>
     
    int w[5] = { 0 , 2 , 3 , 4 , 5 };			//商品的体积2、3、4、5
    int v[5] = { 0 , 3 , 4 , 5 , 6 };			//商品的价值3、4、5、6
    int bagV = 8;					        //背包大小
    int dp[5][9] = { { 0 } };			        //动态规划表
    int item[5];					        //最优解情况
     
    void findMax() {					//动态规划
    	for (int i = 1; i <= 4; i++) {
    		for (int j = 1; j <= bagV; j++) {
    			if (j < w[i])
    				dp[i][j] = dp[i - 1][j];
    			else
    				dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);
    		}
    	}
    }
     
    void findWhat(int i, int j) {				//最优解情况
    	if (i >= 0) {
    		if (dp[i][j] == dp[i - 1][j]) {
    			item[i] = 0;
    			findWhat(i - 1, j);
    		}
    		else if (j - w[i] >= 0 && dp[i][j] == dp[i - 1][j - w[i]] + v[i]) {
    			item[i] = 1;
    			findWhat(i - 1, j - w[i]);
    		}
    	}
    }
     
    void print() {
    	for (int i = 0; i < 5; i++) {			//动态规划表输出
    		for (int j = 0; j < 9; j++) {
    			cout << dp[i][j] << ' ';
    		}
    		cout << endl;
    	}
    	cout << endl;
     
    	for (int i = 0; i < 5; i++)			//最优解输出
    		cout << item[i] << ' ';
    	cout << endl;
    }
     
    int main()
    {
    	findMax();
    	findWhat(4, 8);
    	print();
     
    	return 0;
    }
    
    1. 优化空间复杂度
      以上方法的时间和空间复杂度均为O(VN),其中时间复杂度已经不能再优化了,但空间复杂度却可以优化到O(N)。
      先考虑上面讲的基本思路如何实现,肯定是有一个主循环i=1...N,每次算出来二维数组f[i][0...V]的所有值。那么,如果只用一个数组f[0...V],能不能保证第i次循环结束后f[j]中表示的就是我们定义的状态f[i][j]呢?f[i][j]是由f[i−1][j]和f[i−1][j−w[i]]两个子问题递推而来,能否保证在推f[i][j]时(也即在第i次主循环中推f[j]时)能够得到f[i−1][j]和f[i−1][j−w[i]]的值呢?事实上,这要求在每次主循环中我们以j=V...0的顺序推f[j],这样才能保证推f[j]时f[j−w[i]]保存的是状态f[i−1][j−w[i]]的值。至于为什么下面有详细解释。代码如下:
    for (int i = 1; i <= n; i++) {
         for (int j = m; j >= 1; j--) {
             if (weight[i] <= j) {
                 f[j] = f[j] > f[j - weight[i]] + value[i] ? f[j] : f[j - weight[i]] + value[i];
             }
         }
    }
    

    简化后:

    for (int i = 1; i <= n; i++)
        for (int j = V; j >= w[i]; j--)
            f[j] = max(f[j], f[j - w[i]] + v[i]);
    
    
    1. 初始化的细节问题
      我们看到的求最优解的背包问题题目中,事实上有两种不太相同的问法。有的题目要求"恰好装满背包"时的最优解,有的题目则并没有要求必须把背包装满。这两种问法的区别是在初始化的时候有所不同。
      如果是第一种问法,要求恰好装满背包,那么在初始化时除了f[0]为0其它f[1...V]均设为−∞,这样就可以保证最终得到的f[N]是一种恰好装满背包的最优解。

    这样在最后判断的时候如果dp数组的值为负的话就表示背包不能完全装满

    如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该将f[0...V]全部设为0
    为什么呢?可以这样理解:初始化的f数组事实上就是在没有任何物品可以放入背包时的合法状态。如果要求背包恰好装满,那么此时只有容量为0的背包可能被价值为0的nothing“恰好装满”,其它容量的背包均没有合法的解,属于未定义的状态,它们的值就都应该是−∞了。如果背包并非必须被装满,那么任何容量的背包都有一个合法解“什么都不装”,这个解的价值为0,所以初始时状态的值也就全部为0了。这个小技巧完全可以推广到其它类型的背包问题,后面也就不再对进行状态转移之前的初始化进行讲解。

  • 相关阅读:
    mysql 中文字段排序( UTF8按拼音首字母排序)
    输入输出挂
    HDU 6301 贪心
    HDU1533 最小费用最大流
    POJ 2135 最小费用最大流 入门题
    HDU 6278 主席树(区间第k大)+二分
    HDU3549 最大流 裸题
    2018牛客网暑期ACM多校训练营(第一场)D图同构,J
    POJ 1804 逆序对数量 / 归并排序
    Codeforces Round #489 (Div. 2) B、C
  • 原文地址:https://www.cnblogs.com/Jason66661010/p/12788119.html
Copyright © 2011-2022 走看看