01背包问题

有n个重量和价值分别为w,v,的物品。从这些物品中挑选出总重量不超过W的物品,求所有挑选方案中价值总和的最大值。
限制条件
●1≤n≤100●1≤Wi,V;≤100●1≤W≤10000
输入
n=4
(w,v) = {(2,3),(1, 2),(3,4), (2,2)}W=5
输出
(选择第0、1、3号物品)
这是被称为背包问题的一个著名问题。这个问题要如何求解比较好呢?不妨先用最朴素的方法,针对每个物品是否放人背包进行搜索试试看。这个想法实现后的结果请参见如下代码:

//输入
int n,. W;
int w[MAX_ N],V [MAX_ N] ;
//从第i个物品开始挑选总重小于j的部分

int rec(int i, int j)
{
int res;
if.(i==n)
{
res = 0;
}
else if(j<w[i])
{
res=rec(i+1,j);
}
else
{
res = max(rec(i + 1, j), rec(i + 1, j w[i]) + v[i]) ;
return res ;
}
void solve()
{
printf ( "%dn", rec(0, W) ) ;}

只不过,这种方法的搜索深度是n,而且每-层的搜索都需要两次分支,最坏就需要0(2")的时间,当n比较大时就没办法解了。所以要怎么办才好呢?为了优化之前的算法,我们看下针对样例输人的情形下rec递归调用的情况。

01背包_搜索


如图所示,rec以(3,2)为参数调用了两次。如果参数相同,返回的结果也应该相同,于是第二次调用时已经知道了结果却白白浪费了计算时间。让我们在这里把第一次 计算时的结果记录下来,省略掉第二次以后的重复计算试试看。接下来,我们来仔细研究- -下前面的算法利用到的这个记忆化数组。记dp[i][]为 根据rec的定义,从第i个物品开始挑选总重小于j时,总价值的最大值。于是我们有如下递推式

dp[n][j]= 0

dp[i+1][j] = dp[i+1][j] ( j<w[i])

=max(dp[i + 1][j],dp[i+1][j-w[i]]+v[ij]) (其 他)

如上所示,不用写递归函数,直接利用递推式将各项的值计算出来,简单地用二重循环也可以解决这一问题。

01背包_搜索_02


01背包_数组_03

int dp[MAX_N + 1] [MAX_W + 1]; / DP数组
void solve()
{
for (int i = n-1;i>= O; i--)
{
for (intj=0;j<=W;.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]);
}
}
printf( "%d(n", dp[0] [W] ) ;
}

这是逆推,也可以选择正推,复杂度一样。

01背包_数组_04


注意记得初始化

因为全局数组的内容会被初始化为0,所以前面的源代码中并没有显式地将初项=0进行赋值,不过当一次运行要处理多组输入数据时,必须要进行初始化,这点一定要注意。