How does one go about analyzing the running time of a naive recursive solution to the unbounded knapsack problem? Lots of sources say the naive implementation is "exponential" without giving more detail.
For reference, here's a bit of Python code that implements the brute-force solution. Note that this can run for a long time even on smallish inputs. One of the interesting things about Knapsack is some inputs are lot harder than others.
import random, time
import sys
class Item:
def __init__(self, weight, value):
self.weight = weight
self.value = value
def __repr__(self):
return "Item(weight={}, value={})".format(self.weight, self.value)
def knapsack(capacity):
if capacity==0: return ([], 0)
max_value = 0
max_contents = []
for item in items:
if item.weight <= capacity:
(contents, value) = knapsack(capacity-item.weight)
if value + item.value > max_value:
max_value = value + item.value
max_contents = [item]
max_contents.extend(contents)
return (max_contents, max_value)
def generate_items(n, max_weight, vwratio=1):
items = []
weights = random.sample(range(1,max_weight+1),n)
for weight in weights:
variation = weight/10
value = max(1, int(vwratio*weight + random.gauss(-variation, variation)))
item = Item(weight, value)
items.append(item)
return items
n=30
max_item_weight=100
capacity=100
items = generate_items(n=n, max_weight=max_item_weight, vwratio=1.1)
st = time.time()
solution, value = knapsack(capacity)
print("completed in %f"%(time.time() - st))
Note that this algorithm can be improved upon nicely by memoizing yielding a O(nW) pseudo-polynomial time solution, but I was interested in understanding how to analyze the brute-force algorithm more precisely.