将有界背包问题转换为0/1背包问题


12

我遇到了一个目标是使用动态编程(而不是其他方法)的问题。有一段距离要跨越,并且有一组不同长度的电缆。准确跨越距离所需的最少电缆数量是多少?

在我看来,这似乎是一个背包问题,但是由于可能存在特定长度的倍数,因此这是一个有限制的背包问题,而不是0/1背包问题。(将每个项目的价值视为其权重。)采用幼稚的方法(而不关心搜索空间的扩展),我用来将有界背包问题转换为0/1背包问题的方法很简单将倍数分解为单数并应用著名的动态规划算法。不幸的是,这导致次优的结果。

例如,给定的电缆:
1 x 10ft,
1 x 7ft,
1 x 6ft,
5 x 3ft,
6 x 2ft,
7 x 1ft

如果目标跨度为13英尺,则DP算法将选择7 + 6来跨越该距离。一个贪婪的算法本来会选择10 + 3,但是这对于最少数量的电缆来说是一个平手。尝试跨度15英尺时会出现问题。DP算法最终选择6 + 3 + 3 + 3来获得4条电缆,而贪婪算法只为3条电缆正确地选择了10 + 3 + 2。

无论如何,对转换限制为0/1的光进行一些扫描,似乎是众所周知的将多个项目转换为{p,2p,4p ...}的方法。我的问题是,如果p + 2p + 4p的总和不等于多个项目的数量,此转换如何工作。例如:我有5条3ft电缆。我不能很好地添加{3,2x3,4x3},因为3 + 2x3 + 4x3> 5x3。我应该改为添加{3,4x3}吗?

[我目前正在尝试处理“俄勒冈步道背包问题”论文,但目前看来,这里使用的方法不是动态编程。


1
我认为这是更适合math.stackexchange.com甚至mathoverflow.net
奥德

3
在通用的stackoverflow和这里之间,我感到非常痛苦。阅读两个站点上的FAQ,此站点首先列出数据结构和算法。阅读数学网站的常见问题解答,似乎建议改为在cstheory网站上询问。
蚂蚁

Answers:


1

您的代码中可能有一些错误。我写了Naryshkin提到的DP程序。对于目标跨度13,它报告6 + 7,而对于15,它报告2 + 6 + 7。

# weight: cable length
# total weight: target span
# value: 1 for each cable
# want minimum number of cables, i.e. minimum total value

def knapsack_01_exact_min(weights, values, W):
    # 0-1 knapsack, exact total weight W, minimizing total value
    n = len(weights)
    values = [0] + values
    weights = [0] + weights
    K = [[0 for i in range(W+1)] for j in range(n+1)]
    choice = [[0 for i in range(W+1)] for j in range(n+1)]
    for i in range(1, n+1):
        for w in range(1, W+1):
            K[i][w] = K[i-1][w]
            choice[i][w] = '|'
            if w >= weights[i]:
                t = K[i-1][w-weights[i]]
                if (w==weights[i] or t) and (K[i][w]==0 or t+values[i] < K[i][w]):
                    choice[i][w] = '\\'
                    K[i][w] = t+values[i]
    return K[n][W], choice

def print_choice(choice, weights):
    i = len(choice)-1
    j = len(choice[0])-1
    weights = [0] + weights
    while i > 0 and j > 0:
        if choice[i][j]=='\\':
            print weights[i],
            j -= weights[i]
        i -= 1
    print

lens = [10, 7, 6] + 5*[3] + 6*[2] + 7*[1]
values = (3+5+6+7)*[1]
span = 13
v, choice = knapsack_01_exact_min(lens, values, span)
print "need %d cables to span %d:" % (v,span),
print_choice(choice, lens)

span = 15
v, choice = knapsack_01_exact_min(lens, values, span)
print "need %d cables to span %d:" % (v,span),
print_choice(choice, lens)

如果您调整输入长度的顺序,则可以提供其他最佳解决方案。例如,lens = 5*[3] + 6*[2] + 7*[1] + [10, 7, 6]将给出15 = 10 + 2 + 3。


您从哪里获得if语句:'if(w-weights [i] == 0或t)和(K [i] [w] == 0或t + values [i] <K [i] [w] ):'?如果现在忘记我的DP算法的源代码,但是我没有进行零检查,只需检查'(t + value [i] <K [i] [w])'”
蚂蚁2012年

1
您正在解决确切的总重量,这意味着每当一件物品被拿走时,我们都需要确保达到(当前步骤的)确切的重量。因此,当我们决定选择一个项目时,第二个子句“ t + values [i] <K [i] [w]”确保我们的总价值较小;但在此之前,我们还需要满足要求的权重,即前i-1个项目必须能够满足权重(w-weights [i]),因此第一个子句“ if K [i-1] [w -weights [i]]“(我为此使用了一个临时变量t)。
jsz 2012年

还有两个额外的校验“ w == weights [i]”和“ K [i] [w] == 0”;由于表的初始化方式,它们是必需的;我认为您将能够做到这一点,因此我不会赘述。(我将w-weights [i] == 0更改为w == weights [i];应该更清楚)。
jsz 2012年

1

我所看到的将有限制的背负问题转换为0/1的方式就是拥有多个相同的项目。假设您有以下物品(以重量,实用费计):

  • 2 x 1,2
  • 3 x 2、3

您将使用以下项将其转换为0/1问题:

  • 一二
  • 一二
  • 2 3
  • 2 3
  • 2 3

并使用0/1算法进行求解。您可能会有多个具有相同正确性的解决方案,因此您可以选择任意一个。


现在来谈谈您的电线问题:我将电缆的长度作为导线的长度,并将每根电缆的值完全相同(将其称为1,尽管任何正值都可以使用)。现在,使用您最喜欢的背包求解算法,但是通常在选择最大化价值的(部分)解决方案的地方,选择最小化价值的解决方案。同样,不要考虑总重量不等于容量的所有解决方案。如果有人愿意,我可能可以(尝试)用实际代码编写一个更具体的算法。


是的,这正是我要填写的体重和价值。我在计算最大值,而不是最小值。我刚才按照您的建议更改了代码,以计算最小值,并将DP表的第0行初始化为MAXINT。结果仍然相同,针对背包问题的动态编程解决方案仍然最终选择了6 + 3 + 3 + 3,而不是10 + 3 + 2或7 + 6 + 2。
蚂蚁
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.