Skip to content

Commit 2ea31ba

Browse files
author
Dhanush D Prabhu
committed
Add Branch and Bound solution for 0/1 Knapsack
1 parent 2c15b8c commit 2ea31ba

File tree

1 file changed

+129
-0
lines changed

1 file changed

+129
-0
lines changed
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
"""
2+
Branch and Bound solution for the 0/1 Knapsack problem.
3+
4+
This implementation uses a best-first search strategy and prunes
5+
non-promising branches using an upper bound calculated via the
6+
fractional knapsack (greedy) approach.
7+
8+
References:
9+
https://en.wikipedia.org/wiki/Branch_and_bound
10+
https://en.wikipedia.org/wiki/Knapsack_problem
11+
"""
12+
13+
from dataclasses import dataclass
14+
from typing import List
15+
import heapq
16+
17+
18+
@dataclass
19+
class Item:
20+
weight: int
21+
value: int
22+
23+
24+
@dataclass
25+
class Node:
26+
level: int
27+
profit: int
28+
weight: int
29+
bound: float
30+
31+
32+
def calculate_bound(node: Node, capacity: int, items: List[Item]) -> float:
33+
"""
34+
Calculate the upper bound of profit for a node using
35+
the fractional knapsack approach.
36+
"""
37+
if node.weight >= capacity:
38+
return 0.0
39+
40+
profit_bound = node.profit
41+
total_weight = node.weight
42+
index = node.level + 1
43+
44+
while index < len(items) and total_weight + items[index].weight <= capacity:
45+
total_weight += items[index].weight
46+
profit_bound += items[index].value
47+
index += 1
48+
49+
if index < len(items):
50+
profit_bound += (
51+
(capacity - total_weight)
52+
* items[index].value
53+
/ items[index].weight
54+
)
55+
56+
return profit_bound
57+
58+
59+
def knapsack_branch_and_bound(
60+
capacity: int, weights: List[int], values: List[int]
61+
) -> int:
62+
"""
63+
Solve the 0/1 Knapsack problem using the Branch and Bound technique.
64+
65+
>>> knapsack_branch_and_bound(50, [10, 20, 30], [60, 100, 120])
66+
220
67+
"""
68+
items = [Item(w, v) for w, v in zip(weights, values)]
69+
items.sort(key=lambda item: item.value / item.weight, reverse=True)
70+
71+
priority_queue: list[tuple[float, Node]] = []
72+
73+
root = Node(level=-1, profit=0, weight=0, bound=0.0)
74+
root.bound = calculate_bound(root, capacity, items)
75+
76+
heapq.heappush(priority_queue, (-root.bound, root))
77+
max_profit = 0
78+
79+
while priority_queue:
80+
_, current = heapq.heappop(priority_queue)
81+
82+
if current.bound <= max_profit:
83+
continue
84+
85+
next_level = current.level + 1
86+
if next_level >= len(items):
87+
continue
88+
89+
# Include the next item
90+
include_node = Node(
91+
level=next_level,
92+
weight=current.weight + items[next_level].weight,
93+
profit=current.profit + items[next_level].value,
94+
bound=0.0,
95+
)
96+
97+
if include_node.weight <= capacity:
98+
max_profit = max(max_profit, include_node.profit)
99+
100+
include_node.bound = calculate_bound(include_node, capacity, items)
101+
if include_node.bound > max_profit:
102+
heapq.heappush(priority_queue, (-include_node.bound, include_node))
103+
104+
# Exclude the next item
105+
exclude_node = Node(
106+
level=next_level,
107+
weight=current.weight,
108+
profit=current.profit,
109+
bound=0.0,
110+
)
111+
112+
exclude_node.bound = calculate_bound(exclude_node, capacity, items)
113+
if exclude_node.bound > max_profit:
114+
heapq.heappush(priority_queue, (-exclude_node.bound, exclude_node))
115+
116+
return max_profit
117+
118+
119+
if __name__ == "__main__":
120+
# Example usage
121+
capacity_example = 50
122+
weights_example = [10, 20, 30]
123+
values_example = [60, 100, 120]
124+
125+
print(
126+
knapsack_branch_and_bound(
127+
capacity_example, weights_example, values_example
128+
)
129+
)

0 commit comments

Comments
 (0)