我想解决0-1背包问题,最大权重为〜200k,超过100k元素,最终确定项目集,而不仅仅是确定最佳权重。
在研究0-1背包时,我读到解决此问题的一种常用方法是通过动态编程并创建一个包含子问题最佳解决方案的表,从而将原始问题分成较小的部分,然后在表上进行回溯以确定项目集。可以以记忆有效的方式(如here所述)来计算最大利润,而不考虑所取物品。
这里明显的问题是,对于我所考虑的维度,此方法将消耗比可行的内存更多的内存(需要O(n*W)
空间,其中n
是元素数,{{1 }}是最大容量)。我进一步研究发现,提到了一种解决0-1背包问题的内存有效方法(例如,here,还参见Kellerer,Pferschy和Pisinger的“背包问题”)。
我们首先将项目设置分为两个子集,大小大致相等。给定原始最大权重W
,我们将这两个子集视为自己的背包问题,并以内存有效方式确定这两个子集的最大利润计算的最后一行(详细信息如上)。
下一步是找出将两个子集最佳分割的位置。为此,我们确定两行权重W
和w1
的最大利润。据我了解,维持w2
是至关重要的,因此我要遍历第一行并将索引放在当前索引的另一端。我目前对该步骤的实现如下:
w1 + w2 = W
def split(weights, values, n, w, i):
# s1 is the bigger subset size if n is not even
s1 = n // 2 + (n&1)
s2 = n // 2
row1 = maximum_profit(weights, values, s1, w)
row2 = maximum_profit(weights[s1:], values[s1:], s2, w)
max_profits_for_capacity = [x + y for x, y in zip(row1, row2[::-1])]
max_profits = max(max_profits_for_capacity)
optimal_weight_index = max_profits_for_capacity.index(max_value)
c1 = row1[optimal_weight_index]
c2 = row2[w-optimal_weight_index-1]
和c1
是然后维持c2
时每个子集的最大利润。使用这些值,我们可以递归到每个子集:
c1 + c2 = W
这是描述让我迷失的地方。最终,此代码将以split(weights[:s1], values[:s1], s1, c1, i)
split(weights[s1:], values[s1:], s2, c2, i+s1)
的值递归到n == 1
。在给定项目索引w
和最大(本地)容量i
的情况下,如何确定元素是否包含在内?
我可以提供一个小的示例数据集,以详细说明我的代码的工作方式以及出现问题的地方。非常感谢。
答案 0 :(得分:0)
首先,我想您可能会误解关于c
,w
及其作为容量的关系,但是却从利润列表中获得了c1
和c2
。
对于问题,通过拆分函数的返回值,您可以定义要回答的问题类型。
当您直接将拆分直接带到n == 1
点,并且想要将已拣选项目的索引获取到背包时,只需在此步骤中返回由[0]
或{{1 }}作为输出:
[1]
if n == 1:
if weights[0] < w:
return [1]
return [0]
意味着将项目挑选到结果集中[1]
否则然后在递归[0]
函数的其他步骤中将它们串联为一个,例如:
split
结果是,您将获得大小为def split(..):
..
# since it is lists concatenation
return split(weights[:s1], values[:s1], s1, c1, i) + split(weights[s1:], values[s1:], s2, c2, i+s1)
(用于拆分项目的数量)的列表,其中包含零和一。
总复杂度为:
n
时间,因为我们一直分裂到O(nWlogn)
步骤n == 1
作为内存,因为递归时我们总是只存储结果列表的一部分