Skip to content

Commit 9216a54

Browse files
committed
add readme
1 parent a596ed4 commit 9216a54

File tree

3 files changed

+378
-0
lines changed

3 files changed

+378
-0
lines changed

README.md

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# knapsack-algorithms
2+
3+
This is a simple Python script that demonstrates the following algorithms:
4+
5+
* Optimal greedy solution for [fractional (continuous) knapsack problem](https://en.wikipedia.org/wiki/Continuous_knapsack_problem)
6+
7+
* Recursive top-down solution for the [discrete 0-1 knapsack problem](https://en.wikipedia.org/wiki/Knapsack_problem#Definition)
8+
9+
* [Dynamic programming solution](https://en.wikipedia.org/wiki/Knapsack_problem#0/1_knapsack_problem) for the discrete 0-1 knapsack problem
10+
11+
* Recursive top-down solution with memoization for the discrete 0-1 knapsack problem
12+
13+
The script also counts the number of recursive calls for the recursive
14+
solutions to 0-1 knapsack and the number of iterations for the dynamic
15+
programming solution.
16+
17+
In the 0-1 knapsack problem, you're given a set of items, each of
18+
which has a value and a weight, and a knapsack with a maximum weight
19+
capacity. The object is to select a subset of the items to put into
20+
the knapsack such that the sum of the values of items in the knapsack
21+
is maximized (you can't pick another subset more valuable) but the sum
22+
of the weights of items in the knapsack does not exceed the maximum
23+
weight capacity. For each item, you can either select it once, or not
24+
select it at all; so, you can't select an item more than once, or
25+
select a fraction of an item, for example.
26+
27+
In the fractional knapsack problem, the object is the same, but you're
28+
allowed to select fractions of each item. For example, if an item has
29+
value 20 and weight 40, you can select 0.5 (half) of the item,
30+
yielding a value of 10 and a weight of 20. You can only select at most
31+
one of each item.
32+
33+
## Example Usage
34+
35+
To run the script simply execute it on the command line.
36+
37+
```
38+
./knapsack.py
39+
```
40+
41+
It will produce an output that should look like the file
42+
[knapsack-output.txt](knapsack-output.txt).
43+
44+
To modify the set of items from which you can select to put into the
45+
knapsack, modify the top of the script [knapsack.py](knapsack.py).
46+
47+
```
48+
items = [ [20, 50], [15, 45], [10, 35], [20, 35], [25, 30], [30, 30], [15, 20], [10, 15], [5, 10], [20, 10] ]
49+
weight_capacity = 166
50+
```
51+
52+
Each pair represents an item, the first value is the item's value and
53+
the second is its weight. The variable weight_capacity is the total
54+
weight that can fit into the knapsack.
55+
56+
## License
57+
58+
This project is licensed under the MIT License (see the [LICENSE](LICENSE) file for details).
59+
60+
## Authors
61+
62+
* **Michael Gabilondo** - [mgabilo](https://github.com/mgabilo)

knapsack-output.txt

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#1) Optimal greedy solution for fractional knapsack
2+
3+
1. items are sorted by value-to-weight ratios as below
4+
2. pick items until the current item's weight exceeds weight_capacity
5+
3. for the last item, only pick portion (weight_capacity - weight_current) / weight(item)
6+
7+
https://en.wikipedia.org/wiki/Continuous_knapsack_problem
8+
9+
*** KNAPSACK CONTENTS (REUSED FOR THE OTHER VARIANTS) ***
10+
weight_capacity = 166
11+
(val wt val/wt)
12+
(20 10 2.00)
13+
(30 30 1.00)
14+
(25 30 0.83)
15+
(15 20 0.75)
16+
(10 15 0.67)
17+
(20 35 0.57)
18+
(5 10 0.50)
19+
(20 50 0.40)
20+
(15 45 0.33)
21+
(10 35 0.29)
22+
23+
*** GREEDY-KNAPSACK EXECUTION ***
24+
added item with (value, weight) = (20, 10)
25+
added item with (value, weight) = (30, 30)
26+
added item with (value, weight) = (25, 30)
27+
added item with (value, weight) = (15, 20)
28+
added item with (value, weight) = (10, 15)
29+
added item with (value, weight) = (20, 35)
30+
added item with (value, weight) = (5, 10)
31+
added PORTION 0.32 of item with (value, weight) = (20, 50), contributing only (6.4, 16.00)
32+
current weight of knapsack is 166 (full)
33+
current value of knapsack is 131.4
34+
35+
------------------------------
36+
37+
#2) recursive top-down discrete 0-1 knapsack with no optimization
38+
39+
https://en.wikipedia.org/wiki/Knapsack_problem#Definition
40+
41+
final items in knapsack = [[20, 50], [25, 30], [30, 30], [15, 20], [10, 15], [5, 10], [20, 10]]
42+
number of items taken = 7
43+
value = 125
44+
weight = 165
45+
number of recursive calls = 962
46+
47+
------------------------------
48+
49+
#3) dynamic programming solution to discrete 0-1 knapsack
50+
51+
https://en.wikipedia.org/wiki/Knapsack_problem#0/1_knapsack_problem
52+
53+
max value = 125
54+
number of iterations = (weight_capacity+1) * len(items) = 1670
55+
the number of columns in the table is weight_capacity+1, since it goes from 0 through weight_capacity
56+
this also allows for items with weights in the range [0, weight_capacity]
57+
58+
tracing solution from table
59+
took item with (value, weight) = (20, 10)
60+
took item with (value, weight) = (5, 10)
61+
took item with (value, weight) = (10, 15)
62+
took item with (value, weight) = (15, 20)
63+
took item with (value, weight) = (30, 30)
64+
took item with (value, weight) = (25, 30)
65+
took item with (value, weight) = (20, 50)
66+
value = 125
67+
weight = 165
68+
69+
------------------------------
70+
71+
#4) recursive top-down 0-1 discrete knapsack with memoization
72+
73+
number of recursive calls = 155
74+
final items in knapsack = [[20, 50], [25, 30], [30, 30], [15, 20], [10, 15], [5, 10], [20, 10]]
75+
value = 125
76+
weight = 165

knapsack.py

+240
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
#!/usr/bin/python3
2+
import operator
3+
import sys
4+
5+
items = [ [20, 50], [15, 45], [10, 35], [20, 35], [25, 30], [30, 30], [15, 20], [10, 15], [5, 10], [20, 10] ]
6+
weight_capacity = 166
7+
8+
9+
def value(item): return item[0]
10+
def weight(item): return item[1]
11+
12+
def weight_items(items):
13+
return sum(weight(item) for item in items)
14+
15+
def value_items(items):
16+
return sum(value(item) for item in items)
17+
18+
def value_weight_ratio(item):
19+
return float(value(item)) / weight(item)
20+
21+
def items_with_ratios(items):
22+
return [item + [value_weight_ratio(item)] for item in items]
23+
24+
def sort_items_with_ratios(items):
25+
return sorted(items, key=operator.itemgetter(2), reverse=True)
26+
27+
def fractional_knapsack(items, weight_capacity):
28+
weight_current = 0
29+
value_current = 0
30+
items_sorted = sort_items_with_ratios(items_with_ratios(items))
31+
32+
print('#1) Optimal greedy solution for fractional knapsack\n')
33+
print('1. items are sorted by value-to-weight ratios as below')
34+
print("2. pick items until the current item's weight exceeds weight_capacity")
35+
print("3. for the last item, only pick portion (weight_capacity - weight_current) / weight(item)\n")
36+
37+
print("https://en.wikipedia.org/wiki/Continuous_knapsack_problem")
38+
39+
print('\n*** KNAPSACK CONTENTS (REUSED FOR THE OTHER VARIANTS) ***')
40+
print('weight_capacity = %d' % weight_capacity)
41+
print('(val wt val/wt)')
42+
for item in items_sorted: sys.stdout.write( '(%-4d %-4d %-4.2f)\n' % (value(item), weight(item), value_weight_ratio(item)) )
43+
sys.stdout.write('\n')
44+
45+
print('*** GREEDY-KNAPSACK EXECUTION ***')
46+
items_taken = []
47+
for item in items_sorted:
48+
if weight_current + weight(item) < weight_capacity:
49+
weight_current += weight(item)
50+
value_current += value(item)
51+
items_taken.append(item)
52+
print('added item with (value, weight) = (%s, %s)' % (value(item), weight(item)))
53+
else:
54+
portion = (weight_capacity - weight_current) / float(weight(item))
55+
weight_current += portion*weight(item)
56+
value_current += portion*value(item)
57+
print('added PORTION %.2f of item with (value, weight) = (%s, %s), contributing only (%s, %.2f)' %\
58+
(portion, value(item), weight(item), portion*value(item), portion*weight(item)))
59+
print('current weight of knapsack is %d (full)' % weight_current)
60+
print('current value of knapsack is', value_current)
61+
print('')
62+
break
63+
64+
discrete_knapsack_calls = 0
65+
66+
def discrete_knapsack(items, size, weight_capacity):
67+
# items is the list of [value, weight]
68+
# size is the size of the list
69+
# weight_capacity is the maximum weight the knapsack can hold
70+
71+
global discrete_knapsack_calls
72+
discrete_knapsack_calls += 1
73+
74+
# item is the item in the knapsack we are deciding whether to keep or leave
75+
item = items[size-1]
76+
77+
# item is the only item; decide whether to keep or leave it based on weight
78+
if size == 1:
79+
if weight(item) <= weight_capacity:
80+
return [item]
81+
else:
82+
return []
83+
84+
# size > 1
85+
items_leave = discrete_knapsack(items, size-1, weight_capacity)
86+
87+
items_keep = items_leave
88+
if weight_capacity - weight(item) >= 0:
89+
items_keep = discrete_knapsack(items, size-1, weight_capacity-weight(item)) + [item]
90+
91+
if value_items(items_leave) >= value_items(items_keep):
92+
return items_leave
93+
return items_keep
94+
95+
96+
discrete_knapsack_memo_calls = 0
97+
98+
def discrete_knapsack_memo(items, size, weight_capacity, dptable):
99+
# items is the list of [value, weight]
100+
# size is the size of the list
101+
# weight_capacity is the maximum weight the knapsack can hold
102+
103+
global discrete_knapsack_memo_calls
104+
discrete_knapsack_memo_calls += 1
105+
106+
# item is the item in the knapsack we are deciding whether to keep or leave
107+
item = items[size-1]
108+
109+
# item is the only item; decide whether to keep or leave it based on weight
110+
if size == 1:
111+
if weight(item) <= weight_capacity:
112+
dptable[size][weight_capacity] = [item]
113+
return [item]
114+
else:
115+
dptable[size][weight_capacity] = []
116+
return []
117+
118+
# size > 1
119+
if dptable[size-1][weight_capacity] is not None:
120+
items_leave = dptable[size-1][weight_capacity]
121+
else:
122+
items_leave = discrete_knapsack_memo(items, size-1, weight_capacity, dptable)
123+
124+
items_keep = items_leave
125+
if weight_capacity - weight(item) >= 0:
126+
if dptable[size-1][ weight_capacity-weight(item)] is not None:
127+
items_keep = dptable[size-1][ weight_capacity-weight(item)] + [item]
128+
else:
129+
items_keep = discrete_knapsack_memo(items, size-1, weight_capacity-weight(item), dptable) + [item]
130+
131+
if items_leave >= items_keep:
132+
dptable[size][weight_capacity] = items_leave
133+
return items_leave
134+
135+
dptable[size][weight_capacity] = items_keep
136+
return items_keep
137+
138+
139+
140+
def discrete_knapsack_memo_toplevel(items, size, weight_capacity):
141+
dptable = [[ None for j in range(weight_capacity+1)] for i in range(len(items)+1)]
142+
return discrete_knapsack_memo(items, size, weight_capacity, dptable)
143+
144+
145+
146+
147+
def discrete_knapsack_dp_trace_solution(dptable, items):
148+
149+
150+
item_idx = len(dptable) - 1
151+
weight_idx = len(dptable[item_idx]) - 1
152+
153+
knapsack = []
154+
155+
print('tracing solution from table')
156+
while item_idx != -1:
157+
next_item_idx, next_weight_idx = dptable[item_idx][weight_idx][1]
158+
159+
if weight_idx > next_weight_idx:
160+
print('took item with (value, weight) = (%d, %d)' % (value(items[item_idx]), weight(items[item_idx])))
161+
knapsack.append(items[item_idx])
162+
163+
item_idx, weight_idx = next_item_idx, next_weight_idx
164+
return knapsack
165+
166+
167+
def discrete_knapsack_dp(items, weight_capacity):
168+
dptable = [[[-1, (-1,-1)] for j in range(weight_capacity+1)] for i in range(len(items))]
169+
VALUE = 0
170+
PARENT = 1
171+
172+
steps = 0
173+
for item_idx in range(len(dptable)):
174+
for weight_idx in range(len(dptable[item_idx])):
175+
steps += 1
176+
177+
if item_idx == 0:
178+
if weight_idx >= weight(items[item_idx]):
179+
dptable[item_idx][weight_idx][VALUE] = value(items[item_idx])
180+
else:
181+
dptable[item_idx][weight_idx][VALUE] = 0
182+
else:
183+
value_if_leaveitem = dptable[item_idx - 1][weight_idx][VALUE]
184+
185+
keepitem_weight_idx = weight_idx - weight(items[item_idx])
186+
value_if_keepitem = value_if_leaveitem - 1
187+
if keepitem_weight_idx >= 0:
188+
value_if_keepitem = dptable[item_idx - 1][keepitem_weight_idx][VALUE] + value(items[item_idx])
189+
190+
if value_if_leaveitem >= value_if_keepitem:
191+
dptable[item_idx][weight_idx][VALUE] = value_if_leaveitem
192+
dptable[item_idx][weight_idx][PARENT] = (item_idx - 1, weight_idx)
193+
else:
194+
dptable[item_idx][weight_idx][VALUE] = value_if_keepitem
195+
dptable[item_idx][weight_idx][PARENT] = (item_idx - 1, keepitem_weight_idx)
196+
197+
print('max value =', dptable[len(items)-1][weight_capacity][VALUE])
198+
print('number of iterations = (weight_capacity+1) * len(items) = %d' % ((weight_capacity+1) * len(items)))
199+
print('the number of columns in the table is weight_capacity+1, since it goes from 0 through weight_capacity')
200+
print('this also allows for items with weights in the range [0, weight_capacity]\n')
201+
return discrete_knapsack_dp_trace_solution(dptable, items)
202+
203+
204+
def main():
205+
206+
fractional_knapsack(items, weight_capacity)
207+
208+
print('------------------------------\n')
209+
210+
print('#2) recursive top-down discrete 0-1 knapsack with no optimization\n')
211+
print("https://en.wikipedia.org/wiki/Knapsack_problem#Definition")
212+
print("")
213+
knapsack = discrete_knapsack(items, len(items), weight_capacity)
214+
print('final items in knapsack =', knapsack)
215+
print('number of items taken =', len(knapsack))
216+
print('value =', value_items(knapsack))
217+
print('weight =', weight_items(knapsack))
218+
print('number of recursive calls =', discrete_knapsack_calls)
219+
220+
print('\n------------------------------\n')
221+
222+
print('#3) dynamic programming solution to discrete 0-1 knapsack\n')
223+
print("https://en.wikipedia.org/wiki/Knapsack_problem#0/1_knapsack_problem")
224+
print("")
225+
knapsack = discrete_knapsack_dp(items, weight_capacity)
226+
print('value =', value_items(knapsack))
227+
print('weight =', weight_items(knapsack))
228+
229+
print('\n------------------------------\n')
230+
231+
print('#4) recursive top-down 0-1 discrete knapsack with memoization\n')
232+
knapsack = discrete_knapsack_memo_toplevel(items, len(items), weight_capacity)
233+
print('number of recursive calls =', discrete_knapsack_memo_calls)
234+
print('final items in knapsack =', knapsack)
235+
print('value =', value_items(knapsack))
236+
print('weight =', weight_items(knapsack))
237+
238+
239+
if __name__ == "__main__":
240+
main()

0 commit comments

Comments
 (0)