0-1背包问题是个很经典的问题,规模较小时常用回溯法来类似于枚举的方法进行所有的选择方案的求解,但是由下图显而易见,这个过程中进行了很多次重复性的计算,故完全可以用 动态规划的方式,利用状态转移表提前计算中间过程,空间换时间的方式。
1、回溯法
该方法理解较为容易,主要依靠上述的图,不断走所有的决策路径,找到需求的最大值。
开始先不断递归到最大深度,此时什么都不装,是上述图中第一个叶子结点的状态,计算此时的状态,并作为最大的价值,然后回溯,装进去第i个物品,计算价值,更新最大值。然后回溯到上一轮决策,选择先装第i-1个物品的状态下,不装i 和装i的价值......依次类推,不断回溯遍历,更新最大价值。
同时,添加的价值是否超过最大价值 的 判断,有助于减少无效循环内容,早停法。
#include <iostream>
#include <vector>
using namespace std;
int weight[5] = {2, 2, 4, 6, 3};
int value[5] = {3, 4, 8, 9, 6};
int n = 5; // 物品个数
int w = 9; // 背包最大容量
int maxV = 0;
void recall(int i, int cur_weight, int cur_value) {
if (cur_weight == w || i == n) { // 装满了或者到达最大深度了
if (cur_value > maxV)
maxV = cur_value;
return;
}
recall(i+1, cur_weight, cur_value);
if (cur_weight + weight[i] <= w)
recall(i+1, cur_weight + weight[i], cur_value + value[i]);
}
int main() {
recall(0, 0, 0);
cout << maxV << endl;
return 0;
}
2、动态规划
考虑上述回溯法中,因为存在回溯到上一轮决策的某一个节点,该节点存在很多重复的计算,适合用动态规划来解决。
动态规划中,首先创建状态转移表,行为决策轮数,列为重量,每一轮分别针对一个物品,在上一轮决策的基础上选择放置和不放置,更新大的价值至状态转移表。同时里面也用到了早停,具体可看代码中注释。
#include <iostream>
#include <vector>
using namespace std;
int weight[5] = {2, 2, 4, 6, 3};
int value[5] = {3, 4, 8, 9, 6};
int n = 5; // 物品个数
int w = 9; // 背包最大容量
/* 动态规划问题 states[n][w+1]为状态转移表
* 其中 n代表着决策轮数,并非物品个数
* 只是5个物品 每一轮都是针对一个物品 选择放置或者不放置 总共5轮
* 2^5 刚好满足所有的情况
* w+1 为重量的状态数 总共为 w加上空 共w+1种状态 */
int knapsack() {
int** states = new int*[n]; // 堆区开辟内存
for (int i = 0; i < n; ++i) { // 行为决策轮数 列为重量
states[i] = new int[w+1]; // 存值为价值
}
for (int i = 0; i < n; ++i) { // 初始化status
for (int j = 0; j < w+1; ++j) {
states[i][j] = -1;
}
}
states[0][0] = 0; // 第一轮 不放置时 重量与价值
if (weight[0] < w) {
states[0][weight[0]] = value[0]; //放置物品1时 重量对应的价值
}
for (int i = 1; i < n; ++i) { // 第i轮 也是针对放不放置物品i的问题 也是在上一轮决策的基础上进行 本轮决策
for (int j = 0; j <= w; ++j)
if (states[i-1][j] >= 0) // 用于早停判断,因为物品不可细分的远远,很多重量状态是不存在的
states[i][j] = states[i-1][j]; // 不放物品,保留上一轮
for (int j = 0; j <= w-weight[i]; ++j) { // 重量状态的索引不能大于最大承受,也是为了减少循环判断
if (states[i-1][j] >= 0) {
int v = states[i-1][j] + value[i]; // 放物品,加上这一个的价值
if (v > states[i][j+weight[i]]) // 该放置方式超过之前同重量的价值则更新
states[i][j+weight[i]] = v; // 更新状态转移表
}
}
}
int maxValue = -1;
for (int j = 0; j <= w; ++j) {
if (states[n-1][j] > maxValue)
maxValue = states[n-1][j];
}
// 查看状态转移表
for (int i = 0; i < n; ++i) {
for (int j = 0; j < w+1; ++j){
cout << states[i][j] << " ";
}
cout << endl;
}
return maxValue;
}
int main() {
int maxV = knapsack();
cout << maxV << endl;
return 0;
}