Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calculating items included in branch and bound knapsack

Using a branch and bound algorithm I have evaluated the optimal profit from a given set of items, but now I wish to find out which items are included in this optimal solution. I'm evaluating the profit value of the optimal knapsack as follows (adapted from here):

import Queue

class Node:
    def __init__(self, level, profit, weight):
        self.level = level # The level within the tree (depth)
        self.profit = profit # The total profit
        self.weight = weight # The total weight

def solveKnapsack(weights, profits, knapsackSize):
    numItems = len(weights)
    queue = Queue.Queue()
    root = Node(-1, 0, 0)    
    queue.put(root)

    maxProfit = 0
    bound = 0
    while not queue.empty():
        v = queue.get() # Get the next item on the queue

        uLevel = v.level + 1 
        u = Node(uLevel, v.profit + e[uLevel][1], v.weight + e[uLevel][0])

        bound = getBound(u, numItems, knapsackSize, weights, profits)

        if u.weight <= knapsackSize and u.profit > maxProfit:
            maxProfit = uProfit

        if bound > maxProfit:    
            queue.put(u)

        u = Node(uLevel, v.profit, v.weight)
        bound = getBound(u, numItems, knapsackSize, weights, profits)

        if (bound > maxProfit):
            queue.put(u)
    return maxProfit

# This is essentially the brute force solution to the fractional knapsack
def getBound(u, numItems, knapsackSize, weight, profit):
    if u.weight >= knapsackSize: return 0
    else:
        upperBound = u.profit
        totalWeight = u.weight
        j = u.level + 1
        while j < numItems and totalWeight + weight[j] <= C:
            upperBound += profit[j]
            totalWeight += weights[j]
            j += 1
        if j < numItems:
            result += (C - totalWeight) * profit[j]/weight[j]
        return upperBound 

So, how can I get the items that form the optimal solution, rather than just the profit?

like image 538
Alex Avatar asked Apr 10 '13 13:04

Alex


1 Answers

I got this working using your code as the starting point. I defined my Node class as:

class Node:
    def __init__(self, level, profit, weight, bound, contains):
        self.level = level          # current level of our node
        self.profit = profit
        self.weight = weight        
        self.bound = bound          # max (optimistic) value our node can take
        self.contains = contains    # list of items our node contains

I then started my knapsack solver similarly, but initalized root = Node(0, 0, 0, 0.0, []). The value root.bound could be a float, which is why I initalized it to 0.0, while the other values (at least in my problem) are all integers. The node contains nothing so far, so I started it off with an empty list. I followed a similar outline to your code, except that I stored the bound in each node (not sure this was necessary), and updated the contains list using:

u.contains = v.contains[:]    # copies the items in the list, not the list location
# Initialize u as Node(uLevel, uProfit, uWeight, 0.0, uContains)
u.contains.append(uLevel)    # add the current item index to the list

Note that I only updated the contains list in the "taking the item" node. This is the first initialization in your main loop, preceding the first if bound > maxProfit: statement. I updated the contains list in the if: statement right before this, when you update the value of maxProfit:

if u.weight <= knapsackSize and u.value > maxProfit:
    maxProfit = u.profit
    bestList = u.contains

This stores the indices of the items you are taking to bestList. I also added the condition if v.bound > maxProfit and v.level < items-1 to the main loop right after v = queue.get() so that I do not keep going after I reach the last item, and I do not loop through branches that are not worth exploring.

Also, if you want to get a binary list output showing which items are selected by index, you could use:

taken = [0]*numItems
for item in bestList:
    taken[item] = 1

print str(taken)

I had some other differences in my code, but this should enable you to get your chosen item list out.

like image 79
Engineero Avatar answered Sep 18 '22 22:09

Engineero