背包问题理论基础
二刷复习背包问题理论基础,并且在IDE中实现数据输入和算法求解:
#include<iostream> // header for std
#include<vector>
#include<algorithm> // header for max
void get_bag_inputs(int* n, std::vector<int>& weight, std::vector<int>& value)
{
std::cin >> *n; // 需要先输入物品数量
// weights
for (int i = 0; i < *n; i++)
{
int temp;
std::cin >> temp;
weight.push_back(temp);
}
// values
for (int i = 0; i < *n; i++)
{
int temp;
std::cin >> temp;
value.push_back(temp);
}
}
int bag_algorithm(int& bagweight, std::vector<int>& weights, std::vector<int>& values)
{
int n = weights.size();
std::vector<std::vector<int>> dp(n, std::vector<int>(bagweight+1));
// 初始化
for (int i = 0; i < n; i++)
{
dp[i][0] = 0;
}
for (int j = weights[0]; j < bagweight+1; j++)
{
dp[0][j] = values[0];
}
// dp数组遍历,先遍历物品,再遍历背包
for (int i = 1; i < n; i++)
{
for (int j = 0; j < bagweight+1; j++)
{
if (weights[i] > j)
dp[i][j] = dp[i - 1][j];
else
dp[i][j] = std::max(dp[i - 1][j], dp[i - 1][j - weights[i]] + values[i]);
}
}
return dp[n - 1][bagweight];
}
int main()
{
std::vector<int> weights;
std::vector<int> values;
int bagweight;
std::cin >> bagweight;
int num;
get_bag_inputs(&num, weights, values);
int max_value = bag_algorithm(bagweight, weights, values);
printf("\nmax value is: %d", max_value);
return 0;
}
实现算法的同时也熟悉一下C++的输入输出以及代码风格(之前Python用习惯了)。要注意的是在二维数组中,bagweight包含容量0,所以背包容量这一维的数组长度为bagweight + 1。
滚动数组
从递推公式上看,二维dp数组计算过程中只用到了上一行的数据,空间复杂度可以优化,使用一维数组滚动更新。但使用滚动数组时,背包容量的遍历顺序就不能从左往右遍历了,从左往右遍历会使得同一个物品重复放置(而实际上同一个物品只能放一次),因此需要倒序遍历。
#include<iostream> // header for std
#include<vector>
#include<algorithm> // header for max
void get_bag_inputs(int* n, std::vector<int>& weight, std::vector<int>& value)
{
std::cin >> *n;
// weights
for (int i = 0; i < *n; i++)
{
int temp;
std::cin >> temp;
weight.push_back(temp);
}
// values
for (int i = 0; i < *n; i++)
{
int temp;
std::cin >> temp;
value.push_back(temp);
}
}
int bag_algorithm2(int& bagweight, std::vector<int>& weights, std::vector<int>& values)
{
int n = weights.size();
std::vector<int> dp(bagweight + 1);
// 初始化
for (int j = weights[0]; j < bagweight + 1; j++)
{
dp[j] = values[0];
}
// dp数组遍历,先遍历物品,再遍历背包
for (int i = 1; i < n; i++)
{
for (int j = bagweight; j >= weights[i]; j--)
{
dp[j] = std::max(dp[j], dp[j - weights[i]] + values[i]);
}
}
return dp[bagweight];
}
int main()
{
std::vector<int> weights;
std::vector<int> values;
int bagweight;
std::cin >> bagweight;
int num;
get_bag_inputs(&num, weights, values);
// int max_value = bag_algorithm1(bagweight, weights, values);
int max_value = bag_algorithm2(bagweight, weights, values);
printf("\nmax value is: %d", max_value);
return 0;
}
bag_algorithm2函数的实现更简洁,遍历背包容量时可以只遍历到weights[i],即只看能放下第i个物品的容量。和讲解代码的区别是,这里用的初始化和二维dp相同,并且物品从1开始遍历;讲解中一维数组初始化为0,物品从0开始倒序遍历背包容量,得出的结果是一样的。
应用题 LeetCode 416 分割等和子集
题目链接:416. 分割等和子集 - 力扣(Leetcode)
这道题需要找到集合里能够总和为sum / 2 的子集,将sum / 2类比为背包容量,物品就是集合中的每个数字,重量和价值都是数值大小,套用01背包的滚动数组解法如下:
class Solution:
def canPartition(self, nums: List[int]) -> bool:
s = sum(nums)
if s % 2 != 0:
return False
target = s // 2
dp = [0] * (target + 1)
n = len(nums)
for i in range(n):
for j in range(target, nums[i] - 1, -1):
dp[j] = max(dp[j], dp[j-nums[i]] + nums[i])
return True if dp[-1] == target else False
如果遍历结束后,背包容量为sum / 2的时候刚好能装满,就是可以获得符合要求的子集,时间和空间复杂度和纯背包问题相同。