01背包问题--动态规划解法(2)(转载)

本章主要讲述最简单的背包问题,从如何建立状态方程到如何根据状态方程来实现代码,再到如何优化数据结构,让我们对动态规划的建立与求解认识更加透彻

题目:

有N件物品和一个容量为V的背包。放入第i件物品的费用是Ci,得到的价值是Wi。求解将哪些物品装入背包可使价值和最大。

分析:

(一)建立状态方程

这是最基础的背包问题,直接说状态转移方程了,设dp[i][v]表示前i件物品放入容量为v的背包能获得的最大价值,每件物品可以选择放与不放,则有:

dp[i][v]=max{dp[i-1][v],dp[i-1][v-Ci]+W[i]}

稍微解释一下,当第i件物品不放时,则dp[i][v]=前i-1件物品恰好放入v容量的背包,即dp[i-1][v],当i件物品放时,那就说明前i-1件物品放的容量为v-Ci,这样才能正好将第i件物品放进去,即dp[i-1][v-Ci]+Wi

(二)根据状态方程实现代码

状态方程建好之后,下面就是如何将它用代码实现,要考虑的主要问题就是:在给dp[i][v]赋值前,dp[i-1][v]和dp[i-1][v-Ci]必须已经赋过值了,带着这个问题我们来看,对于这样一个转移方程,二维的,肯定需要至少两层遍历,需要考虑两个方面:

1、这两个二维变量是从前向后遍历还是从后向前遍历

2、这两个变量的先后遍历顺序

先看第一个方面:

看到方程里有两个变量,i与v,先看i,i的关系是靠i-1推过来的,所以i毫无疑问一定是从0开始到n遍历,再看v,v一定是比v-Ci大的,所以v也一定是从最小的值0开始到最大的值V,

然后再来看顺序,即是先遍历i还是先遍历v的问题,我们看到这个状态转移方程的i只与i-1有关,即i变量只变动了1,而此时v变量是从v-Ci到的v,中间变动了Ci,所以此问题毫无疑问,i在外层循环,v在里层循环

最后我们在做下稍微的调整,我们要确保i-1和v-Ci是合法的,所以v不能从0开始了,要从Ci开始,至于i-1,i可以从1开始遍历,用下标1来表示第一件物品,就无需对i-1处理了

根据上面分析,代码就能写出来了,实现代码如下:

[cpp] view plaincopy

  1. #include <iostream>
  2. #include <algorithm>
  3. using namespace std;
  4. #define N 3//物品个数
  5. #define V 10//背包容量
  6. int dp[N+1][V+1];
  7. int C[N+1]={0,10,5,5};  //Ci表示第i件物品的费用   (i从1开始)
  8. int W[N+1]={0,2,2,1};   //W[i]表示第i件物品的价值
  9. int ZeroOnePack(){
  10. for(int i=1;i<=N;i++){
  11. for(int v=C[i];v<=V;v++)
  12. dp[i][v]=max(dp[i-1][v],dp[i-1][v-C[i]]+W[i]);
  13. }
  14. return dp[N][V];
  15. }
  16. int main()
  17. {
  18. memset(dp,0,sizeof(dp));//先对dp初始化
  19. cout<<ZeroOnePack();
  20. return 0;
  21. }

(三)数据结构优化

接下来,我们要考虑如何对此算法进行优化,

我们先对空间复杂度进行优化

考虑到i只与i-1有关,所以我们可以用滚动数组的方法,将二维压缩到一维,那么我们要做的就是把i这一维去掉,即当前的i对应的dp[v]是由上一状态i-1对应的dp[v]和dp[v-Ci]得到,那么我们在对当前状态的dp[v]进行赋值时,必须得确保这个dp[v]在内层for循环执行完之前是不能再用到的,如果内层for循环是从Ci开始到V,那么对于dp[v]=max(dp[v],dp[v-Ci]),我们看到max里面的v一定是上一状态i-1对应的dp[v],因为这个dp[v]我们还没用到,(当然了,此时我们已经赋值过的v应是从Ci到v-1),但是对于v-C[i]就不行了,因为v-C[i]很可能就包含在Ci到v-1中,这些值已经变动过了,不再是i-1状态所对应的值了,所以为了保证v-Ci的状态还是之前的状态,我们必须得从后向前遍历,为什么?还是回到方程dp[v]=max(dp[v],dp[v-Ci]),如果是从后向前遍历,那么我们已经赋过值的dp[v]是(dp[V],dp[V-1]......dp[v+1]),那么dp[v]是没动过的,可以,对于dp[v-Ci],v-Ci<v,一定不在(v+1,V)中,所以成立
做了以上分析,就可以实现代码了

[cpp] view plaincopy

  1. #include <iostream>
  2. #include <algorithm>
  3. using namespace std;
  4. #define N 3//物品个数
  5. #define V 10//背包容量
  6. int C[N+1]={0,10,5,5};  //Ci表示第i件物品的费用   (i从1开始)
  7. int W[N+1]={0,2,2,1};   //W[i]表示第i件物品的价值
  8. int dp[V+1];
  9. int ZeroOnePack(){
  10. for(int i=1;i<=N;i++){
  11. for(int v=V;v>=C[i];v--)
  12. dp[v]=max(dp[v],dp[v-C[i]]+W[i]);
  13. }
  14. return dp[V];
  15. }
  16. int main()
  17. {
  18. memset(dp,0,sizeof(dp));//先对dp初始化
  19. cout<<ZeroOnePack();
  20. return 0;
  21. }

然后我们看时间复杂度,在时间复杂度方面,因为必定要访问到所有的i和所有的v,所以n^2复杂度在所难免,那么我们可不可以以减少些循环的次数?答案是肯定的

由于我们只需要求最终结果dp[V],对于dp[V-1]、dp[V-2]……我们是不需要知道的,但是上面的代码执行完,经过我们以上的分析,这些值是存在的,所以我们可以感觉到,是不是做了些额外的劳动呢?我们来从最终的结果dp[V]往前找找线索,还是这个公式,我们从最后的V向前看:

已知最后一步一定做的是这一步:dp[V]=max(dp[V],dp[V-C[N]]+W[N]),我们看出了dp[V]只与dp[V-C[N]]有关,而比容量V-C[N]小的值是不需要计算的,这也是为什么能求出dp[V-1]、dp[V-2]……d的原因,依次在往前推,在i=N-1时,此时的dp[V]=max(dp[V],dp[V-C[N-1]]+W[N-1]),c此时的V也只与V-C[N-1]有关,由于此前v需要遍历的最小值是V-C[N],这里在此基础之上又需知道V-C[N-1]的值,所以v需要的遍历的最小值也只要是V-(C[N]+C[N-1])就行了,…………规律看出来了吧,话不多说,贴代码:

[cpp] view plaincopy

[cpp] view plaincopy

  1. #include <iostream>
  2. #include <algorithm>
  3. using namespace std;
  4. #define N 3//物品个数
  5. #define V 10//背包容量
  6. int C[N+1]={0,10,5,5}; //Ci表示第i件物品的费用 (i从1开始)
  7. int W[N+1]={0,2,2,1}; //W[i]表示第i件物品的价值
  8. int dp[V+1];
  9. int sum[N+1]={0};//辅助数组,用于求C[N]+C[N-1]……
  10. int ZeroOnePack(){
  11. //常数优化
  12. sum[N]=C[N];
  13. for(int i=N-1;i>=1;i--)sum[i]+=sum[i+1]+C[i];
  14. //end
  15. for(int i=1;i<=N;i++){
  16. for(int v=V;v>=max(V-sum[i],C[i]);v--)
  17. dp[v]=max(dp[v],dp[v-C[i]]+W[i]);
  18. }
  19. return dp[V];
  20. }
  21. int main()
  22. {
  23. memset(dp,0,sizeof(dp));//先对dp初始化
  24. cout<<ZeroOnePack();
  25. return 0;
  26. }

(四)状态方程深入剖析

适才我们从最终的dp[V]向前讨论,怎么那么像递推的思想呢,由于只需求dp[V]的解,那么递归能否解决呢

还是从这个方程开始dp[v]=max(dp[v],dp[v-C[i]]+W[i]),max中前者指的是不取i获得的值,后者指的是取i获得的值,由此可以写递归代码了

[cpp] view plaincopy

  1. #include <iostream>
  2. #include <algorithm>
  3. using namespace std;
  4. #define N 3//物品个数
  5. #define V 10//背包容量
  6. int C[N+1]={0,10,5,5};  //Ci表示第i件物品的费用   (i从1开始)
  7. int W[N+1]={0,2,2,1};   //W[i]表示第i件物品的价值
  8. int ZeroOnePack(int i,int v){
  9. if(i<=0||v<=0)return 0;
  10. //不选第i件物品
  11. int a=ZeroOnePack(i-1,v);
  12. //必须得保证剩下的容量v能够有C[i]的容量,才能选择第i件物品
  13. int b=v>=C[i]?ZeroOnePack(i-1,v-C[i])+W[i]:0;
  14. return max(a,b);
  15. }
  16. int main()
  17. {
  18. cout<<ZeroOnePack(N,V);
  19. return 0;
  20. }

(五)变形问题

将题目略微改动一下,附加一个条件,即求恰好装满容量为v的背包所获得的最大值,那么我们该如何求解呢?

我们还是从状态方程开始,从最初的方程说吧,dp[i][v]=max(dp[i-1][v],dp[i-1][v-C[i]]+W[i]),这里的v就要指恰装满v的背包了,我们来看从上一状态i-1怎么转移到当前状态的,dp[i-1][v]和dp[i-1][v-C[i]]两个值,因为这里要求装满背包,我们无法保证dp[i-1][v]和dp[i-1][v-C[i]]是存在的,即前i-1件物品不一定装满容量v的背包或者是容量v-C[i]的背包,但是如果其中一个是能装满的,另一个不能装满,则一定是选那个能装满的,再看是取这两个值得max值,那么我们就有法了,将初态除dp[0][0]=0外,dp[0][1……V]=负无穷,即1……V的背包起始状态是无效态

[cpp] view plaincopy

  1. #include <iostream>
  2. #include <algorithm>
  3. #include <vector>
  4. using namespace std;
  5. #define INF -0x7ffffff
  6. #define N 3//物品个数
  7. #define V 10//背包容量
  8. int C[N+1]={0,10,4,5};  //Ci表示第i件物品的费用   (i从1开始)
  9. int W[N+1]={0,2,2,1};   //W[i]表示第i件物品的价值
  10. vector<int> dp;
  11. int ZeroOnePack(){
  12. for(int i=1;i<=N;i++){
  13. for(int v=V;v>=C[i];v--)
  14. dp[v]=max(dp[v],dp[v-C[i]]+W[i]);
  15. }
  16. return dp[V];
  17. }
  18. int main()
  19. {
  20. dp.assign(V+1,INF);//dp初始化为负无穷
  21. dp[0]=0;
  22. cout<<ZeroOnePack();
  23. return 0;
  24. }

递归代码实现也只要稍微改一改条件就行了

[cpp] view plaincopy

  1. #include <iostream>
  2. #include <algorithm>
  3. using namespace std;
  4. #define INF -0x7fffffff
  5. #define N 3//物品个数
  6. #define V 10//背包容量
  7. int C[N+1]={0,10,4,5};  //Ci表示第i件物品的费用   (i从1开始)
  8. int W[N+1]={0,2,2,1};   //W[i]表示第i件物品的价值
  9. int ZeroOnePack(int i,int v){
  10. if(v==0)return 0;
  11. if(i==0)return INF;
  12. //不选第i件物品
  13. int a=ZeroOnePack(i-1,v);
  14. //必须得保证剩下的容量v能够有C[i]的容量,才能选择第i件物品
  15. int b=v>=C[i]?ZeroOnePack(i-1,v-C[i])+W[i]:INF;
  16. return max(a,b);
  17. }
  18. int main()
  19. {
  20. cout<<ZeroOnePack(N,V);
  21. return 0;
  22. }

时间: 2024-12-15 05:29:20

01背包问题--动态规划解法(2)(转载)的相关文章

01背包问题--动态规划解法

从01背包问题理解动态规划 01背包问题具体例子:假设现有容量10kg的背包,另外有3个物品,分别为a1,a2,a3.物品a1重量为3kg,价值为4:物品a2重量为4kg,价值为5:物品a3重量为5kg,价值为6.将哪些物品放入背包可使得背包中的总价值最大? 这个问题有两种解法,动态规划和贪婪算法.本文仅涉及动态规划. 先不套用动态规划的具体定义,试着想,碰见这种题目,怎么解决? 首先想到的,一般是穷举法,一个一个地试,对于数目小的例子适用,如果容量增大,物品增多,这种方法就无用武之地了. 其次

《算法导论》读书笔记之第16章 0-1背包问题—动态规划求解

原文:http://www.cnblogs.com/Anker/archive/2013/05/04/3059070.html 1.前言 前段时间忙着搞毕业论文,看书效率不高,导致博客一个多月没有更新了.前段时间真是有些堕落啊,混日子的感觉,很少不爽.今天开始继续看算法导论.今天继续学习动态规划和贪心算法.首先简单的介绍一下动态规划与贪心算法的各自特点及其区别.然后针对0-1背包问题进行讨论.最后给出一个简单的测试例子,联系动态规划实现0-1背包问题. 2.动态规划与贪心算法 关于动态规划的总结

01背包问题-动态规划算法

转 https://www.cnblogs.com/Christal-R/p/Dynamic_programming.html 一.问题描述:有n 个物品,它们有各自的重量和价值,现有给定容量的背包,如何让背包里装入的物品具有最大的价值总和? 二.总体思路:根据动态规划解题步骤(问题抽象化.建立模型.寻找约束条件.判断是否满足最优性原理.找大问题与小问题的递推关系式.填表.寻找解组成)找出01背包问题的最优解以及解组成,然后编写代码实现: 三.动态规划的原理及过程: eg:number=4,ca

0-1背包问题——动态规划

1 // 动态规划法解决0-1背包问题 2 //example: 3 //物品种类n=5,背包容量c=10, 4 //物品的重量向量 w={2,2,6,5,4},物品的价值向量 v={6,3,5,4,6} 5 // O(min{n*c,2^n}) 6 #include "stdafx.h" 7 #include <cstdlib> 8 #include <iostream> 9 10 using namespace std; 11 template<cla

背包问题基本解法 —— 《背包九讲》笔记

相对于转载文章,我更喜欢写上一篇笔记,开篇给出原文链接.这样,能有些自己的东西,总结一番,对知识的理解能加深一层:别人看来,也更有价值. 今天做USACO题目时,一道题不会,网上查到解法是01背包,于是重新看了<背包九讲>.相比第一次看,理解深的多,可见我还是在进步的,只要我没停下脚步.如果大家想看原文,那么只需要百度“背包九讲”就好了,百度文库中的“背包九讲 2.0”是正版,作者是崔添翼前辈,网上好像称他为dd大牛.这篇文章可以说是“背包问题”的权威了,如果我了解无误的话,背包问题的整套解法

0-1背包问题与分数背包问题

0-1背包问题与分数背包问题 问题描述 问题分析之分数背包 代码设计之分数背包问题 问题分析之0-1背包问题 代码设计之0-1背包问题 动态规划算法之间的差别 0-1背包问题与分数背包问题 我们在文章<贪心算法原理>:http://blog.csdn.net/ii1245712564/article/details/45369491中提到过动态规划和贪心算法的区别.以及两个经典的例子:0-1背包问题和分数背包问题,我么知道0-1背包问题是不能够使用贪心算法求解的,而贪心算法则是分数背包问题的不

ACM1881 01背包问题应用

01背包问题动态规划应用 acm1881毕业bg 将必须离开的时间限制看作背包容量,先将他们由小到大排序,然后在排完序的数组中对每个实例都从它的时间限制开始(背包容量)到它的延长时间进行遍历: 1 #include<iostream> 2 #include<algorithm> 3 #include<cstring> 4 using namespace std; 5 struct BG 6 { 7 int h,t,l; 8 friend bool operator<

【动态规划】01背包问题_两种解法

问题描述 0-1背包问题:给定\(n\)种物品和一背包.物品i的重量是\(w_i\),其价值为\(v_i\),背包的容量为\(C\).问:应该如何选择装入背包的物品,使得装人背包中物品的总价值最大? 在选择装人背包的物品时,对每种物品\(i\)只有两种选择,即装人背包或不装入背包.不能将物品\(i\)装入背包多次,也不能只装入部分的物品\(i\).因此,该问题称为0-1背包问题. 此问题的形式化描述是,给定\(C>0\),\(w_i>0\),\(v_i>0\),\(1≤i≤n\),要求找

01背包问题的动态规划算法

01背包问题我最初学会的解法是回溯法,第一反应并不是用动态规划算法去解答.原因是学习动态规划算法的时候,矩阵连乘.最长公共子串等问题很容易将问题离散化成规模不同的子问题,比较好理解,而对于01背包问题则不容易想到将背包容量离散化抽象出子问题,从情感上先入为主也误以为动态规划算法不是解决01背包问题的好方法,实际上并不是这样的.另外,动态规划算法不对子问题进行重复计算,但是要自底向上将所有子问题都计算一遍,直到计算出最终问题的结果也就是我们要的答案,有点像爬山的感觉. 问题描述:给定n种物品和一背