初探01背包问题
2016-05-23 22:36
218 查看
01背包问题是DP总最基本的问题,作为刚刚入门的小菜,01背包问题让我对DP有了一定的认识,同时提高了问题的分析能力。
代码还仅仅停留在直接思维的过程,是最初级的处理01背包问题的代码,比较便于理解。代码如下:
看过背包问题的基本样式后,我们可以试着简化代码,以下部分解释十分详尽,转自http://blog.csdn.net/insistgogo/article/details/8579597
-----------------------------------------------------------------------------------------------------------------------------------
优化空间复杂度
上述的方法,我们使用二维数组 f[i][v] 保存中间状态,这里我们可以使用一维数组f[v]保存中间状态就能得到结果
分析
我们现在使用f[v]保存中间状态,我们想要达到的效果是,第i次循环后,f[v]中存储的是前i个物体放到容量v时的最大价值
在回顾下之前讲过的状态转移方程:
[cpp] view
plain copy
f[i][v] = max(f[i - 1][v],f[i - 1][v - weight[i]] + cost[i])
我们可以看到,要想得到 f[i][v],我们需要知道 f[i - 1][v] 和 f[i - 1][v - weight[i]],由于我们使用二维数组保存中间状态,所以可以直接取出这两个状态。
当我们使用一维数组存储状态时,f[v]表示,在执行i次循环后(此时已经处理i个物品),前i个物体放到容量v时的最大价值,即之前的f[i][v]。与二维相比较,它把第一维隐去了,但是二者表达的含义还是相同的,只不过针对不同的i,f[v]一直在重复使用,所以,也会出现第i次循环可能会覆盖第i - 1次循环的结果。
为了求f[v],我们需要知道,前i - 1个物品放到容量v的背包中带来的收益,即之前的f[i - 1][v] 和 前i - 1件物品放到容量为v - weight[i]的背包中带来的收益,即之前的f[i - 1][v - weight[i]] + cost[i]。
难点:由于我们只使用一维数组存储,则在求这两个子问题时就没有直接取出那么方便了,因为,第i次循环可能会覆盖第i - 1次循环的结果。
现在我们来求这两个值
1)前i - 1个物品放到容量v的背包中带来的收益,即之前的f[i - 1][v] :
由于,在执行在i次循环时,f[v]存储的是前i个物体放到容量v时的最大价值,在求前i个物体放到容量v时的最大价值(即之前的f[i][v])时,我们是正在执行第 i 次循环,f[ v ]的值还是在第 i - 1 次循环时存下的值,在此时取出的 f[ v
]就是前i - 1个物体放到容量v时的最大价值,即f[i - 1][v]。
2)前i - 1件物品放到容量为v - weight[i]的背包中带来的收益,即之前的f[i - 1][v - weight[i]] + cost[i]
由于,在执行第i次循环前,f[0 ~ V]中保存的是第i - 1次循环的结果,即是前i - 1个物体分别放到容量0 ~ V时的最大价值,即f[i - 1][0 ~ V]。
则,在执行第i次循环前,f 数组中v - weight[i]的位置存储就是我们要找的 前i - 1件物品放到容量为v - weight[i]的背包中带来的收益 (即之前的f[i - 1][v - weight[i]]),这里假设物品是从数组下标1开始存储的。
伪代码
[cpp] view
plain copy
for i=1..N //枚举物品
for v=V..0 //枚举容量,从大到小
f[v]=max{f[v],f[v-weight[i]] + cost[i]};
由上面伪代码可知,在执行第 i 次循环时,需要把背包容量由V..0都要遍历一遍,检测第 i 件物品是否能放。
逆序枚举容量的原因:
注意一点,我们是由第 i - 1 次循环的两个状态推出 第 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背包,按照增序枚举背包容量是不对的。
代码
[cpp] view
plain copy
#include <iostream>
using namespace std;
const int N = 3;//物品个数
const int V = 5;//背包最大容量
int weight[N + 1] = {0,3,2,2};//物品重量
int value[N + 1] = {0,5,10,20};//物品价值
int f[V + 1] = {0};
int Max(int x,int y)
{
return x > y ? x : y;
}
/*
目标:在不超过背包容量的情况下,最多能获得多少价值
子问题状态:f[j]:表示前i件物品放入容量为j的背包得到的最大价值
状态转移方程:f[j] = max{f[j],f[j - weight[i]] + value[i]}
初始化:f数组全设置为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()
{
cout<<Knapsack()<<endl;
system("pause");
return 1;
}
但是,增序枚举背包容量会达到什么效果:它会重复的装入某个物品,而且尽可能多的,使价值最大,当然不会不超过背包容量
-----------------------------------------------------------------------------------------------------------------------------------
通过上述文章讲解,可以明白如何用一维数组实现二维数组的功能,其原因就在于状态转移式f[i][v]主要依赖于f[i-1],
同时还明白了一位数组实现背包问题,对背包要逆序遍历的原因,因为一旦被遍历访问,f[i-1][v]就会变成f[i][v]。
参考博客:
http://www.cnblogs.com/xy-kidult/archive/2013/03/25/2970313.html http://blog.csdn.net/insistgogo/article/details/8579597
代码还仅仅停留在直接思维的过程,是最初级的处理01背包问题的代码,比较便于理解。代码如下:
#include <iostream> using namespace std; int Max(int A,int B) //求出最大值 { return A>B?A:B; } int main() { int n; int bag,matter; int value[100]={}; int cost[100]={}; int f[100][100]={}; int max=-1; cin>>n; //输入数据的组数 while(n--) { max=-1; //记录最大值 cin>>bag>>matter; //输入背包容量和物品数量 for(int k=1;k<=matter;k++) //输入物品价值和重量 { cin>>value[k]>>cost[k]; } for(int i=1;i<=matter;i++) //从前1个开始遍历前matter个物品 { for(int j=0;j<=bag;j++) //从容积为0开始遍历背包的容积到bag { f[i][j]=f[i-1][j]; //默认将当前的最大价值置为不放第i个物品时的值 if(j>=cost[i]) //如果背包有空间可以放当前第i个物品 { //比较不放第i个的时候(不放i)和 以减小i-1容积后再加上第i个物品价值的总价值(放i)的大小 //确保当前背包可用的空间是一定的 f[i][j]=Max(f[i-1][j],f[i-1][j-cost[i]]+value[i]); max=f[i][j]>max?f[i][j]:max;//记录最大值 } } } } cout<<max<<endl; cin>>n; }
看过背包问题的基本样式后,我们可以试着简化代码,以下部分解释十分详尽,转自http://blog.csdn.net/insistgogo/article/details/8579597
-----------------------------------------------------------------------------------------------------------------------------------
优化空间复杂度
上述的方法,我们使用二维数组 f[i][v] 保存中间状态,这里我们可以使用一维数组f[v]保存中间状态就能得到结果
分析
我们现在使用f[v]保存中间状态,我们想要达到的效果是,第i次循环后,f[v]中存储的是前i个物体放到容量v时的最大价值
在回顾下之前讲过的状态转移方程:
[cpp] view
plain copy
f[i][v] = max(f[i - 1][v],f[i - 1][v - weight[i]] + cost[i])
我们可以看到,要想得到 f[i][v],我们需要知道 f[i - 1][v] 和 f[i - 1][v - weight[i]],由于我们使用二维数组保存中间状态,所以可以直接取出这两个状态。
当我们使用一维数组存储状态时,f[v]表示,在执行i次循环后(此时已经处理i个物品),前i个物体放到容量v时的最大价值,即之前的f[i][v]。与二维相比较,它把第一维隐去了,但是二者表达的含义还是相同的,只不过针对不同的i,f[v]一直在重复使用,所以,也会出现第i次循环可能会覆盖第i - 1次循环的结果。
为了求f[v],我们需要知道,前i - 1个物品放到容量v的背包中带来的收益,即之前的f[i - 1][v] 和 前i - 1件物品放到容量为v - weight[i]的背包中带来的收益,即之前的f[i - 1][v - weight[i]] + cost[i]。
难点:由于我们只使用一维数组存储,则在求这两个子问题时就没有直接取出那么方便了,因为,第i次循环可能会覆盖第i - 1次循环的结果。
现在我们来求这两个值
1)前i - 1个物品放到容量v的背包中带来的收益,即之前的f[i - 1][v] :
由于,在执行在i次循环时,f[v]存储的是前i个物体放到容量v时的最大价值,在求前i个物体放到容量v时的最大价值(即之前的f[i][v])时,我们是正在执行第 i 次循环,f[ v ]的值还是在第 i - 1 次循环时存下的值,在此时取出的 f[ v
]就是前i - 1个物体放到容量v时的最大价值,即f[i - 1][v]。
2)前i - 1件物品放到容量为v - weight[i]的背包中带来的收益,即之前的f[i - 1][v - weight[i]] + cost[i]
由于,在执行第i次循环前,f[0 ~ V]中保存的是第i - 1次循环的结果,即是前i - 1个物体分别放到容量0 ~ V时的最大价值,即f[i - 1][0 ~ V]。
则,在执行第i次循环前,f 数组中v - weight[i]的位置存储就是我们要找的 前i - 1件物品放到容量为v - weight[i]的背包中带来的收益 (即之前的f[i - 1][v - weight[i]]),这里假设物品是从数组下标1开始存储的。
伪代码
[cpp] view
plain copy
for i=1..N //枚举物品
for v=V..0 //枚举容量,从大到小
f[v]=max{f[v],f[v-weight[i]] + cost[i]};
由上面伪代码可知,在执行第 i 次循环时,需要把背包容量由V..0都要遍历一遍,检测第 i 件物品是否能放。
逆序枚举容量的原因:
注意一点,我们是由第 i - 1 次循环的两个状态推出 第 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背包,按照增序枚举背包容量是不对的。
代码
[cpp] view
plain copy
#include <iostream>
using namespace std;
const int N = 3;//物品个数
const int V = 5;//背包最大容量
int weight[N + 1] = {0,3,2,2};//物品重量
int value[N + 1] = {0,5,10,20};//物品价值
int f[V + 1] = {0};
int Max(int x,int y)
{
return x > y ? x : y;
}
/*
目标:在不超过背包容量的情况下,最多能获得多少价值
子问题状态:f[j]:表示前i件物品放入容量为j的背包得到的最大价值
状态转移方程:f[j] = max{f[j],f[j - weight[i]] + value[i]}
初始化:f数组全设置为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()
{
cout<<Knapsack()<<endl;
system("pause");
return 1;
}
但是,增序枚举背包容量会达到什么效果:它会重复的装入某个物品,而且尽可能多的,使价值最大,当然不会不超过背包容量
-----------------------------------------------------------------------------------------------------------------------------------
通过上述文章讲解,可以明白如何用一维数组实现二维数组的功能,其原因就在于状态转移式f[i][v]主要依赖于f[i-1],
同时还明白了一位数组实现背包问题,对背包要逆序遍历的原因,因为一旦被遍历访问,f[i-1][v]就会变成f[i][v]。
参考博客:
http://www.cnblogs.com/xy-kidult/archive/2013/03/25/2970313.html http://blog.csdn.net/insistgogo/article/details/8579597
相关文章推荐
- 简单的四则运算
- 数的奇偶性
- ACMer博客瀑布流分析
- ACM程序设计大赛题目分类
- 计算字符串最后一个单词长度
- ACM网址
- 1272 小希的迷宫
- 1272 小希的迷宫
- hdu 1250 大数相加并用数组储存
- 矩阵的乘法操作
- 蚂蚁爬行问题
- 蚂蚁爬行问题
- 求两个数的最大公约数【ACM基础题】
- 打印出二进制中所有1的位置
- 杭电题目---一只小蜜蜂
- HDOJ 1002 A + B Problem II (Big Numbers Addition)
- 初学ACM - 半数集(Half Set)问题 NOJ 1010 / FOJ 1207
- 初学ACM - 组合数学基础题目PKU 1833
- POJ ACM 1002
- 高精度加法——杭电1002