0-1背包问题是比较经典的动态规划,题意如下:
给定n件物品的重量和对应价值,给定一个背包的最大承重,求该背包所能装下的物品的最大价值,并输出哪几件装进了背包。
这个问题可以用一个二维数组来做,这样非常直观明了。但是有些情况下,数据过大,采用二维数组会超空间,所以需要采用一维数组进行空间压缩,复杂度是不变的,要注意的是填充一维数组时需要倒序。每次更新一遍一维数组,其实就相当于二维数组的更新。
下面是二维数组的方法,一维数组的方法在其后:
最关键的一个公式是
d[i][j] = max(d[i - 1][j], d[i - 1][j - w[i]] + v[i]);
其中,d[i][j]是指在前i件物品中,选择若干件放在承重为j的背包中,可以取得的最大价值。
先给出d[i][j]这个矩阵的示意图,因为我觉得在编程实现
过程中一直想着这个矩阵,思路会清晰很多。
这里,我统一把数组的第0个元素置为零,不使用(即:在上面的矩阵中,第0列全是0,不使用,就没有画出来。数组中的1就代表第一个)
代码如下,注释说的很清楚了:
#include <iostream>
#include<algorithm>
using namespace std;
//d[i][j]是一个矩阵,表示在前i件物品中,选择若干件放在承重为j的背包中,可以取得的最大价值
int main(){
int w[6] = {0, 2, 2, 6, 5, 4 }; //6件物品的重量
int v[6] = {0, 6, 3, 5, 4, 6 }; //6件物品的价值
int c = 10; //背包的最大承重量
int a[6] = { 0 }; //用来存储第i个物品是否装入背包
int d[6][11] = { 0 }; //矩阵
//接下来的双层for循环,用来填充矩阵
for (int j = 0; j <= 10; j++)
d[0][j] = 0;
for (int i = 1; i <= 5; i++){
for (int j = 0; j <= 10; j++) {
if (j < w[i]){
//如果背包装不了第i件,最大价值相当于和第i-1件的时候一样
d[i][j] = d[i - 1][j];
}
else{
d[i][j] = max(d[i - 1][j], d[i - 1][j - w[i]] + v[i]); //这个公式是核心!
}
}
}
//接下来的for循环,用来回溯,找到哪个物品放入了背包
int j = 10;
for (int i = 5; i > 0; i--){
//如果第i个和第i-1个不一样,说明第i个加进来了
if (d[i][j] > d[i - 1][j]){
a[i] = 1;
j = j - w[i];
}
else
a[i] = 0;
}
cout << d[5][10] << endl;
for (int i = 1; i <= 5; i++)
cout << a[i] << " ";
cout << endl;
return 0;
}
填充一个一维数组的过程其实就是更新N遍,第i遍更新相当于上面的前i个物品容量不同时的最大价值。
下面是代码:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <stdio.h>
using namespace std;
int main(){
int w[6] = { 0, 2, 2, 6, 5, 4 }; //6件物品的重量
int v[6] = { 0, 6, 3, 5, 4, 6 }; //6件物品的价值
int c = 10; //背包的最大承重量
int dp[11] = { 0 };
for (int i = 0; i < 6; i++){
for (int j = c; j >= w[i]; j--){ //对这个数组倒序填充
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}
}
cout << dp[c] << endl;
return 0;
}