用C++解决0-N Knapsack问题

144 阅读4分钟

在这篇文章中,我们将学习一种在C++中解决0-N knapsack问题的有效方法。这个问题是最流行的经典动态编程问题之一。虽然我们在本文中也可以用递归的方法来解决这个问题,但我们将只介绍这个问题的动态编程解决方案。

0-N Knapsack问题

这只是对0-1 knapsack问题的一个小修改。

问题说明

有一个包含N个物品的列表,其中有它们的价格和权重。给你一个有限容量的袋子,比如说W,你必须把袋子装满,使袋子里的物品的总价格在所有可能的组合中达到最大。假设每种物品的供应量都是无限的,你可以任意选择任何物品。

这个问题非常简单,但它的解决方法却有点棘手。天真的递归方法将直接把我们引向一个非常低效的路径,因为它涉及调查和比较所有可能的解决方案,并选择最好的。这意味着对于N个项目来说,总共会有2N种组合,而浏览所有这些组合是一个指数级的复杂任务。这个想法将计算限制在最多15-20个项目。然而,这是非常差的复杂性,在现实生活中几乎没有用处。因此,我们需要更好的东西。让我们转向动态编程方法来更有效地解决这个问题。

0 N Knapsack

0-N Knapsack

使用动态编程的Knapsack算法

由于这是一个最大化的问题,我们可以使用一个一维的DP(动态编程)数组来解决这个问题。当我们有重复出现的子问题时,动态编程是非常有用的。而在背包中,有很多重复的子问题。因此,我们创建一个数组来存储所有子问题的解决方案。下面是一个例子来澄清你的理解。

允许的最大重量。W = 20kg

重量={5,10,15}。

值 = {10, 30, 20}

重量和数值的可能组合。

  1. {5, 5, 5, 5}
  2. {10, 10}
  3. {10, 5, 5}
  4. {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 Algorithm Code

0-N Knapsack算法代码

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

Driver Code

驱动程序代码

输出

0 N Knapsack Program Output

0-N Knapsack程序输出

总结

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