1

I tried the implementation given here for knapsack problem using Branch and Bound.

The solution seems fine but it doesn't give the final selected items to reach the optimal value. Is there a way to get this by changing the following code minimally?

import sys

def bound(vw, v, w, idx):
    if idx >= len(vw) or w > limit:
        return -1
    else:
        while idx < len(vw) and w + vw[idx][1] <= limit:
            v, w, idx = v + vw[idx][0], w + vw[idx][1], idx + 1
        if idx < len(vw):
            v += (limit - w) * vw[idx][0] / (vw[idx][1] * 1.0)
        return v

def knapsack(vw, limit, curValue, curWeight, curIndex):
    global maxValue
    if bound(vw, curValue, curWeight, curIndex) >= maxValue:
        if curWeight + vw[curIndex][1] <= limit:
            maxValue = max(maxValue, curValue + vw[curIndex][0])
            knapsack(vw, limit, curValue + vw[curIndex][0], curWeight + vw[curIndex][1], curIndex + 1)

    if curIndex < len(vw) - 1:
        knapsack(vw, limit, curValue, curWeight, curIndex + 1)

    return maxValue

maxValue = 0

if __name__ == '__main__':
    with open(sys.argv[1] if len(sys.argv) > 1 else sys.exit(1)) as f:
        n, limit = map(int, f.readline().split())
        vw = []
        taken = n * [0]
        for ln in f.readlines():
            vl, wl = map(int, ln.split())
            vw.append([vl, wl, vl / (wl * 1.0)])
    print(knapsack(sorted(vw, key=lambda x: x[2], reverse=True), limit, 0, 0, 0))
    print(taken)

Lets say we have an input file with following contents

4 11
8 4
15 8
4 3
10 5

I am expecting a result like the following

19
0 1 1 0

I wrote my own implementation which gives the above desired output but it's taking too long for large problems like this one

Vinay
  • 1,149
  • 3
  • 16
  • 28

1 Answers1

2

I reorganised the provided code a bit, and then added the logic to keep track of the optimal selection. As you want that selection to be a list of zeroes and ones, I used a bitmap for it (a big integer), and each item gets a bit assigned in that bitmap.

Here is how it would look:

from collections import namedtuple

Item = namedtuple("Item", "value, weight, bit")

def knapsack(items, limit):
    maxValue = 0
    bestTaken = 0
    
    def bound(value, weight, index):
        if index >= len(items) or weight > limit:
            return -1
        else:
            item = items[index]
            while weight + item.weight <= limit:
                value, weight, index = value + item.value, weight + item.weight, index + 1
                if index >= len(items):
                    return value
                item = items[index]
            else:
                return value + (limit - weight) * item.value / item.weight

    def recur(taken, value, weight, index):
        nonlocal maxValue, bestTaken

        if maxValue < value:
            maxValue = value
            bestTaken = taken

        if index < len(items) and bound(value, weight, index) >= maxValue:
            item = items[index]
            if weight + item.weight <= limit:
                recur(taken | item.bit, value + item.value, weight + item.weight, index + 1)
            recur(taken, value, weight, index + 1)

    # Add bit mask for each item:
    items = [Item(*item, 1 << index) for index, item in enumerate(items)]
    items.sort(key=lambda item: -item.value / item.weight)
    recur(0, 0, 0, 0)
    return maxValue, ('{:0{width}b}').format(bestTaken, width=len(items))[::-1]

if __name__ == '__main__':
    # Demo input
    s = """4 11
           8 4
           15 8
           4 3
           10 5"""

    lines = s.splitlines(False)
    _, limit = map(int, lines.pop(0).split())
    items = [tuple(map(int, line.split())) for line in lines]
    value, bits = knapsack(items, limit)
    print("Maximised value:", value)
    print("Item selection:", bits)
trincot
  • 317,000
  • 35
  • 244
  • 286
  • I didn't quite understand the bit part but it worked. But the problem is it doesn't solve problem with 10000 items due to the large big int required. Is there an alternative solution? – Vinay Jun 04 '21 at 07:16
  • Well, knapsack problems cannot be solved in polynomial time. From your question I understood that you were happy with this algorithm, and just needed it to return the selected items. If now you have another question -- about performance of your selected algorithm, then the question becomes different. I cannot help you with that. – trincot Jun 04 '21 at 07:21
  • Okay. Suppose a large problem is solved for optimal solution without returning selected items in a small time, does identifying selected items increase the solving time significantly. I am assuming it will not. In that case the bitmap solution is being limited by the size of problem not because it is hard to solve but because we could not assign a large enough bit. That's why I asked for an alternate solution. But thanks for solution anyways. – Vinay Jun 04 '21 at 08:09
  • No, I tested the code in your question, with the modified code I present here. There is no significant difference in efficiency. Of course, there is a little bit of overhead, but it is little in comparison with the overall execution times. – trincot Jun 04 '21 at 08:27
  • Coming back to your first comment. You can use sets instead of big integers, or just lists with the selected items. At the end of the algo you can convert such a list to the 0-1 list you want to have. – trincot Jun 04 '21 at 14:32