文章是在观摩了博主xiajiawei0206《01背包问题 总结关于为什么01背包优化成1维数组后,内层循环是逆序的》的基础上进行撰写了,感谢博主提供。因此本文很多问题原型和数据原型便直接饮用博主的,不想自己花费时间再去琢磨。
博主xiajiawei0206博文连接:https://blog.csdn.net/xiajiawei0206/article/details/19933781
一、01背包问题原型
1、01背包问题的提出
有N件物品和一个容量为V的背包。第i件物品的重量是c[i],价值是w[i]。
求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和最大。
(01背包中这些物品每种都只有1个,每个物品只能装一次)
2、基本解决思路
这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。
如果用F[i][v]表示第i件物品放入容量为v的背包里可以获得的最大价值,则该问题的递推表达式为:
F[i][v]=max{F[i-1][v],F[i-1][v-c[i]]+w[i]} (01背包问题的精髓所在)
表达式中的i为第i件物品,我们的物品是从i=1,2.....n进行排序递增的。
对该递推式子的理解为:如果我们考虑到第i件物品的时候,可以根据背包容量选择放或者不放,如果不放第i件物品,那么F[i][v]=F[i-1][v],即“第i件物品放入容量为v的背包里可以获得的最大价值”和“前i-1件物品放入容量为v的背包里可以获得的最大价值”是等同的;如果放置第i件物品,那么F[i][v]=F[i-1][v-c[i]]+w[i],即“第i件物品放入容量为v的背包里可以获得的最大价值”和“放置第i件物品后的剩余空间v-c[i]可以继续放置前i-1件物品的最大价值”。为了F[i][v]最大,所以取两者较大值。
3、基本解决思路C++实现
//空间复杂度为IV
void IV_ZEROONEPAC(int num,int *w,int *c,int dp[][MAX_V])
{
for (int i = 1; i <= num; i++)
{
cout << "物品" << i << ":";
cin >> c[i] >> w[i];
}
for (int i = 1; i <= num; i++)
{
for (int v = 0; v <= MAX_V; v++)//问题所在
{
if (v >= c[i])
dp[i][v] = max(dp[i - 1][v], dp[i - 1][v - c[i]] + w[i]);
else dp[i][v] = 0;
cout << dp[i][v] << "\t";
}
cout << "\n";
}
}
二、空间复杂度优化
1、优化原理
原式子(二维的): f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}-----------------------------式子(1)
现在要改成一维的(空间优化): f[v]=max{ f[v],f[v-c[i]]+w[i]}--------------------式子(2)
注意上面的状态转移方程两边的是2个状态(左边的是这一状态 右边的是上一状态(二维的通过i可以看出来))
开始的时候始终不能明白式子(2),其实当我们i从1,2,3....n的时候,计算第i次f[v]时,等式(2)右值f[v]是上一次迭代(i-1次)产生的值(这句话是重点,明白了就能理解优化问题的本质了)。比如我们考虑一元函数g(x[i])=2g(x[i-1]),当我们i递增时,设初始值i=0,g(x0)=1,则:i=1,g(x[1])=2g(x[0])=2;i=2,g(x[2])=2g(x[1])=4,.........如果我们省略i不写,则:i=1,g(x)=2g(x)=2;i=2,g(x)=2g(x)=4,也就是说后面的g(x)是保存的上一次g(x)的值。
2、数据举例(用数学等式将每一步都写下来,离你理解01背包问题也不远了)
关于等式(2),开篇连接博主证明v顺序递增是不行的,大家可以去看看,会有启发的。下面的数据也是引用的原博主的,只是跟他用不同的思路去递推。
设有3件物品 ,背包能容纳的总重量为10,物品序号为i=1,2,3
物品号 重量(c) 价值(w)
i=1 4 5
i=2 7 9
i=3 5 6
a、用式子(1)去递推(优化前)
当i=1时,v=4~10:
f[1][4]=max{f[0][4],f[0][0]+5}=5
f[1][5]=max{f[0][5],f[0][1]+5}=5
f[1][6]=max{f[0][6],f[0][2]+5}=5
f[1][7]=max{f[0][7],f[0][3]+5}=5
f[1][8]=max{f[0][8],f[0][4]+5}=5
f[1][9]=max{f[0][9],f[0][5]+5}=5
f[1][10]=max{f[0][10],f[0][6]+5}=5
当i=2时,v=7~10:
f[2][7]=max{f[1][7],f[1][0]+9}=9
f[2][8]=max{f[1][8],f[1][1]+9}=9
f[2][9]=max{f[1][9],f[1][2]+9}=9
f[2][10]=max{f[1][10],f[1][3]+9}=9
当i=3时,v=5~10:
f[3][5]=max{f[2][5],f[2][0]+8}=8
f[3][6]=max{f[2][6],f[2][1]+8}=8
f[3][7]=max{f[2][7],f[2][2]+8}=9
f[3][8]=max{f[2][8],f[2][3]+8}=9
f[3][9]=max{f[2][9],f[2][4]+8}=9
f[3][10]=max{f[2][10],f[5][3]+8}=9
b、用式子(2)去递推(优化后)
当i=1时,v=10~4:
f[10]=max{f[10],f[6]+5}=5(右式的f[10]之前没出现过,初始值为0)
f[9]=max{f[9],f[5]+5}=5
f[8]=max{f[8],f[4]+5}=5
f[7]=max{f[7],f[3]+5}=5
f[6]=max{f[6],f[2]+5}=5
f[5]=max{f[5],f[1]+5}=5
f[4]=max{f[4],f[0]+5}=5
当i=2时,v=10~7:
f[10]=max{f[10],f[3]+9}=9
f[9]=max{f[9],f[2]+9}=9
f[8]=max{f[8],f[1]+9}=9
f[7]=max{f[7],f[0]+9}=9
当i=3时,v=10~5:
f[10]=max{f[10],f[5]+8}=9
f[9]=max{f[9],f[4]+8}=9
f[8]=max{f[8],f[3]+8}=9
f[7]=max{f[7],f[2]+8}=9
f[6]=max{f[6],f[1]+8}=8
f[5]=max{f[5],f[0]+8}=8
网上的参考的一小段话:f[i][v]只与f[i-1][v]和f[i-1][v-C[i]]有关,即只和i-1时刻状态有关,所以我们只需要用一维数组f[]来保存i-1时的状态f[]。假设i-1时刻的f[]为{a0,a1,a2,…,av},难么i时刻的f[]中第v个应该为max(av,av-C[i]+W[i])即max(f[v],f[v-C[i]]+W[i]),这就需要我们遍历V时逆序遍历,这样才能保证求i时刻f[v]时f[v-C[i]]是i-1时刻的值。如果正序遍历则当求f[v]时其前面的f[0],f[1],…,f[v-1]都已经改变过,里面存的都不是i-1时刻的值,这样求f[v]时利用f[v-C[i]]必定是错的值。最后f[V]即为最大价值。
3、C++实现
//空间复杂度为V
void V_ZEROONEPAC(int num_v, int *w_v, int *c_v, int dp[])
{
for (int i = 1; i <= num_v; i++)
{
cout << "物品" << i << ":";
cin >> c_v[i] >> w_v[i];
}
for (int i = 1; i <= num_v; i++)
{
for (int v = MAX_V; v >= 0; v--)
{
if (v >= c_v[i]) dp[v] = max(dp[v], dp[v - c_v[i]] + w_v[i]);
else dp[v] = 0;
cout << dp[v] << "\t";
}
cout << "\n";
}
}
三、C++源代码
#include<iostream>
using namespace std;
#define MAX_V 10
int max(int a,int b)
{
if (a > b) return a;
else return b;
}
//空间复杂度为IV
void IV_ZEROONEPAC(int num,int *w,int *c,int dp[][MAX_V])
{
for (int i = 1; i <= num; i++)
{
cout << "物品" << i << ":";
cin >> c[i] >> w[i];
}
for (int i = 1; i <= num; i++)
{
for (int v = 0; v <= MAX_V; v++)//问题所在
{
if (v >= c[i])
dp[i][v] = max(dp[i - 1][v], dp[i - 1][v - c[i]] + w[i]);
else dp[i][v] = 0;
cout << dp[i][v] << "\t";
}
cout << "\n";
}
}
//空间复杂度为V
void V_ZEROONEPAC(int num_v, int *w_v, int *c_v, int dp[])
{
for (int i = 1; i <= num_v; i++)
{
cout << "物品" << i << ":";
cin >> c_v[i] >> w_v[i];
}
for (int i = 1; i <= num_v; i++)
{
for (int v = MAX_V; v >= 0; v--)
{
if (v >= c_v[i]) dp[v] = max(dp[v], dp[v - c_v[i]] + w_v[i]);
else dp[v] = 0;
cout << dp[v] << "\t";
}
cout << "\n";
}
}
int main()
{
int num = 0;
cout << "请输入物品件数:";
cin >> num;
int w[MAX_V] = { 0 };
int c[MAX_V] = { 0 };
int dq[MAX_V] = { 0 };
int dp[MAX_V][MAX_V] = { { 0 }, { 0 } };
IV_ZEROONEPAC(num, w, c, dp);
cout << "==============================================================\n";
V_ZEROONEPAC(num, w, c, dq);
return 0;
}
//源代码中还有一个自己不能理解也不能解决的问题
//问题:当for (int v = 0; v <= MAX_V; v++)中v从1开始,则dp[3,0]=9,
//dp[3][5]=13,结果不对,讲道理,dp[0][0]=0。