[杂记] 01背包记录路径
[杂记] 01背包记录路径
众所周知,01背包的时间复杂度是\(O(nm)\)(n为物品数量,m为背包容量),空间复杂度是\(O(m)\)。如果还需要输出最优解中的所有物品的话,时间复杂度不变,空间复杂度呢?
你的第一反应可能是:我很快就可以给出一个空间复杂度也是\(O(m)\)的算法啊?
但实际上这个算法是有问题的:
int n; // 物品数量
int m; // 背包容量
int w[N]; // 物品重量
int v[N]; // 物品价值
int dp[M]; // dp数组
int pre[M]; // 路径
void package01() {
for (int i = 1; i <= n; i ++) {
for (int j = m; j >= w[i]; j --) {
if (dp[j] < dp[j-w[i]] + v[i]) {
dp[j] = dp[j-w[i]] + v[i];
pre[j] = i;
}
}
}
int j = m;
while (pre[j] != 0) {
int last = pre[j];
printf("%d, ", last);
j -= w[last];
}
}
这样写有什么问题呢?
当然有!
比如对于下面的样例:
n = 3;
m = 4;
w[] = {1, 1, 2};
v[] = {1, 1, 4};
输出结果就是
3, 3,
第三个物品被用了两次。
这时你可能恍然大悟,因为在第三个物品加入后,pre[2]被更新为了3,所以就丢失了前两个物品的路径。
本质原因在于,一开始的01背包本来就是二维数组\(dp[i][j]\),表示“只使用前i个物品,背包容量为j时的最大价值”,这时\(pre[i][j]\)的意义就是“只使用前i个物品,背包容量为j,达到最大价值时最后一个购买的物品”。转移是:
if (dp[i-1][j] > dp[i-1][j-w[i]] + v[i]) {
dp[i][j] = dp[i-1][j-w[i]] + v[i];
pre[i][j] = i;
}
else {
dp[i][j] = dp[i-1][j];
pre[i][j] = pre[i-1][j];
}
路径是:
int i = n, j = m;
while (pre[i][j] != 0) {
int last = pre[i][j];
printf("%d, ", last);
i = last - 1;
j -= w[last];
}
如果把pre压缩为1维,相当于最后只剩下\(pre[n][.]\),前面\(pre[<n][.]\)的路径信息就丢失了,这样就没法输出完整路径了。
那有没有时间复杂度还是\(O(nm)\),空间复杂度低于\(O(nm)\)的做法呢?
好吧我是没想出来。
不过倒是有一种能让空间降低32倍的做法:将pre变成一个01数组,\(pre[i][j]=1\)表示“只使用前i个物品,背包容量为j,达到最大价值时最后一个购买的物品就是i”。这样也能输出完整的路径。
int i = n, j = m;
while (i != 0) {
while (!pre[i][j])
i --;
printf("%d, ", i);
j -= w[i];
i --;
}
如果有高人能给出空间复杂度低于\(O(nm)\)的做法,欢迎交流。
| 欢迎来原网站坐坐! >原文链接<