在这篇文章中,我们将学习一种在C++中解决0-N knapsack问题的有效方法。这个问题是最流行的经典动态编程问题之一。虽然我们在本文中也可以用递归的方法来解决这个问题,但我们将只介绍这个问题的动态编程解决方案。
0-N Knapsack问题
这只是对0-1 knapsack问题的一个小修改。
问题说明
有一个包含N个物品的列表,其中有它们的价格和权重。给你一个有限容量的袋子,比如说W,你必须把袋子装满,使袋子里的物品的总价格在所有可能的组合中达到最大。假设每种物品的供应量都是无限的,你可以任意选择任何物品。
这个问题非常简单,但它的解决方法却有点棘手。天真的递归方法将直接把我们引向一个非常低效的路径,因为它涉及调查和比较所有可能的解决方案,并选择最好的。这意味着对于N个项目来说,总共会有2N种组合,而浏览所有这些组合是一个指数级的复杂任务。这个想法将计算限制在最多15-20个项目。然而,这是非常差的复杂性,在现实生活中几乎没有用处。因此,我们需要更好的东西。让我们转向动态编程方法来更有效地解决这个问题。

0-N Knapsack
使用动态编程的Knapsack算法
由于这是一个最大化的问题,我们可以使用一个一维的DP(动态编程)数组来解决这个问题。当我们有重复出现的子问题时,动态编程是非常有用的。而在背包中,有很多重复的子问题。因此,我们创建一个数组来存储所有子问题的解决方案。下面是一个例子来澄清你的理解。
允许的最大重量。W = 20kg
重量={5,10,15}。
值 = {10, 30, 20}
重量和数值的可能组合。
- {5, 5, 5, 5}
- {10, 10}
- {10, 5, 5}
- {15, 5}
请注意,在案例1和3中,我们在选择{5,5}和{10}后有重复的子问题,因为现在我们再次选择相同的{5,5}组合。为了避免这些子问题的重复计算,我们使用动态编程。
解决knapsack的步骤是。
- 将一个大小为
W + 1的数组初始化为0。 - 这个数组的每一个单元格都代表袋子的可能权重,从0到W。
- 我们优化每个权重的值,以自下而上的方式建立这个数组。
- 这个数组的最后一个元素将包含重量W的最大值。
我们来看看如何填充这个数组
- 开始一个for循环,遍历数组的所有索引,即从0到N。
- 现在,对于每个权重值,我们为每个权重启动另一个for循环
- 这个内部for循环遍历所有的N个项目
- 如果当前考虑的项目的权重小于i(代表当前权重),那么我们将简单地放弃当前i值的这个项目。
- 否则,权重将是。
max(dp[i - arr[j] + value[i], dp[i])
这个算法基本上就这样了,一旦我们离开了这两个循环,我们就会返回数组的最后一个元素,即return arr[W] 。让我们快速跳到代码中,看看算法的工作情况。
用C++解决0-N Knapsack问题的代码
#include <iostream>
#include <vector>
using namespace std;
int knapsack(vector <int> weights, vector <int> values, int W)
{
// initializing the dp array
vector <int> dp(W + 1, 0);
for(int i = 0; i < W + 1; i++)
{
//cout << "Evaluating for the weight: " << i << endl;
for(int j = 0; j < weights.size(); j++)
{
//cout << "Index: " << j << endl;
//cout << "Weight at this index: " << weights[j] << endl;
// consider the current element
// only if its weight is less than
// the current weight under consideration
if(weights[j] <= i)
{
int curr_value = dp[i];
//cout << "Value at this index: " << values[j] << endl;
//cout << "Current maximum value for weight: " << i << " is: " << dp[i] << endl;
// now is the logic to
// maximize the weight
dp[i] = max(dp[i - weights[j]] + values[j], dp[i]);
//if(dp[i] != curr_value)
//cout << "After this comparision the value increased from: " << curr_value << " to: " << dp[i] << endl;
}
}
}
cout << "The subproblem array is:" << endl;
for(int value : dp)
cout << value << " ";
cout << endl;
cout << "Maximum profit for current list of items is: " << dp[W] << endl;
return dp[W];
}
int main()
{
vector <int> weights;
cout << "Enter the weights of items |Press -1 to stop|" << endl;
while(true)
{
int weight;
cin >> weight;
if(weight == -1)
break;
weights.push_back(weight);
}
vector <int> values;
cout << "Now enter the value of each item |Press -1 to stop|" << endl;
while(true)
{
int value;
cin >> value;
if(value == -1)
break;
values.push_back(value);
}
cout << "Enter the target weight" << endl;
int W;
cin >> W;
knapsack(weights, values, W);
}

0-N Knapsack算法代码
请注意,在上面的图片中,代码包含了几个cout 语句,即控制台输出语句,这些语句的目的是帮助你理解代码的每一个步骤。在运行这段代码时,你可以取消对这些语句的注释,然后编译该程序,然后运行该程序就可以得到该算法的详细信息。

驱动程序代码
输出

0-N Knapsack程序输出
总结
在这篇文章中,我们学习了如何实现0-N knapsack问题的算法。我们使用了动态编程的方法。外层for循环运行W次,内层for循环在外层for循环的每次迭代中运行N次。因此,该算法的有效时间复杂度被推导为O(N.W) 。今天就到这里,谢谢大家的阅读。