背包分支定界错误结果

Posted

技术标签:

【中文标题】背包分支定界错误结果【英文标题】:knapsack branch and bound wrong result 【发布时间】:2017-05-14 15:42:10 【问题描述】:

我已将this link 给出的代码转换为python 版本。该代码应该计算要填充在重量W的背包中的最大值的正确值。我附上了以下代码:

#http://www.geeksforgeeks.org/branch-and-bound-set-2-implementation-of-01-knapsack/

from queue import Queue
class Node:
    def __init__(self):
        self.level = None
        self.profit = None
        self.bound = None
        self.weight = None

    def __str__(self):
        return "Level: %s Profit: %s Bound: %s Weight: %s" % (self.level, self.profit, self.bound, self.weight)


def bound(node, n, W, items):
    if(node.weight >= W):
        return 0

    profit_bound = int(node.profit)
    j = node.level + 1
    totweight = int(node.weight)

    while ((j < n) and (totweight + items[j].weight) <= W):
        totweight += items[j].weight
        profit_bound += items[j].value
        j += 1

    if(j < n):
        profit_bound += (W - totweight) * items[j].value / float(items[j].weight)

    return profit_bound

Q = Queue()

def KnapSackBranchNBound(weight, items, total_items):
    items = sorted(items, key=lambda x: x.value/float(x.weight), reverse=True)

    u = Node()
    v = Node()

    u.level = -1
    u.profit = 0
    u.weight = 0

    Q.put(u)
    maxProfit = 0;

    while not Q.empty():
        u = Q.get()
        if u.level == -1:
            v.level = 0

        if u.level == total_items - 1:
            continue

        v.level = u.level + 1
        v.weight = u.weight + items[v.level].weight
        v.profit = u.profit + items[v.level].value
        if (v.weight <= weight and v.profit > maxProfit):
            maxProfit = v.profit;

        v.bound = bound(v, total_items, weight, items)
        if (v.bound > maxProfit):
            Q.put(v)

        v.weight = u.weight
        v.profit = u.profit
        v.bound = bound(v, total_items, weight, items)
        if (v.bound > maxProfit):
            # print items[v.level]
            Q.put(v)

    return maxProfit

if __name__ == "__main__":
    from collections import namedtuple
    Item = namedtuple("Item", ['index', 'value', 'weight'])
    input_data = open("test.data").read()
    lines = input_data.split('\n')

    firstLine = lines[0].split()
    item_count = int(firstLine[0])
    capacity = int(firstLine[1])

    print "running from main"
    items = []

    for i in range(1, item_count+1):
        line = lines[i]
        parts = line.split()
        items.append(Item(i-1, int(parts[0]), float(parts[1])))
    kbb = KnapSackBranchNBound(capacity, items, item_count)
    print kbb

程序应该为文件test.data中的以下项目计算235的值:

5 10
40 2
50 3.14
100 1.98
95 5
30 3

第一行显示number of itemsknapsack weight。第一行下面的行显示了这些项目的valueweight。物品使用namedtuple 制作,并根据价值/重量进行分类。对于这个问题,我得到的是 135 而不是 235。我在这里做错了什么?

编辑: 我已经解决了基于分支定界查找正确项目的问题。有需要的可以查看here

【问题讨论】:

你是如何运行你的函数来获得 135 输出的?在您的示例中,哪些项目组合应给出 235?你有一个函数输出正确的小例子吗? 我已将代码修改为包含 Items 的 namedtuples。文件test.data 包含上述信息。 值为 40、95 和 100 的项目将为此代码提供 235 的值。 您可以通过设置 input_data = """粘贴文件内容""" 并注释掉 open(filename).read() 来运行它。当然,如果使用 python 2,队列模块是 Queue。此外,我将 str 更改为 repr 以更好地使用我的调试器。 【参考方案1】:

问题是您将多个对同一Node() 对象的引用插入到您的队列中。修复方法是在 while 循环的每次迭代中初始化两个新的 v 对象,如下所示:

    while not Q.empty():
        u = Q.get()
        v = Node()                                  # Added line
        if u.level == -1:
            v.level = 0

        if u.level == total_items - 1:
            continue

        v.level = u.level + 1
        v.weight = u.weight + items[v.level].weight
        v.profit = u.profit + items[v.level].value
        if (v.weight <= weight and v.profit > maxProfit):
            maxProfit = v.profit;

        v.bound = bound(v, total_items, weight, items)
        if (v.bound > maxProfit):
            Q.put(v)

        v = Node()                                  # Added line
        v.level = u.level + 1                       # Added line
        v.weight = u.weight
        v.profit = u.profit
        v.bound = bound(v, total_items, weight, items)
        if (v.bound > maxProfit):
            # print(items[v.level])
            Q.put(v)

如果不进行这些重新初始化,您将修改已插入队列的v 对象。 这与 C++ 不同,在 C++ 中,Node 对象是隐式复制到队列中的值,以避免诸如此类的别名问题。

【讨论】:

是的,看起来我是在直接修改节点,而无需像在 C++ 版本中那样复制它们。你现在知道有什么方法可以回溯这个版本的解决方案吗?谢谢 我得到了同样的结果,虽然如果你定义节点(级别,重量,利润)看起来更好。您还可以用一个简单的列表替换 Queue(用于同步线程)。 @Pant 这听起来像是一项新任务,您应该尝试自己解决。你试过什么,你卡在哪里? (提示:使用新属性扩展节点以跟踪已获取的项目。) 我试图累积哪些节点的收益超过了 maxProfit。但是,这会给出所有那些节点,其中一个是最佳值。我找不到一种方法来获取其中哪些是最佳节点。 @Pant 这里是另一个提示:尝试使用新属性 v.chosen 扩展代码,使得 v.profit == sum(items[i].value for i in v.chosen) 和 v。 weight == sum(items[i].weight for i in v.chosen)。

以上是关于背包分支定界错误结果的主要内容,如果未能解决你的问题,请参考以下文章

分支定界背包 Java

分支定界法改良背包

编译c代码时出错

Python背包分支和绑定

如何在 C++ 中正确使用优先级队列

运筹优化中的分支定界算法