Skip to content

Commit a6fb347

Browse files
author
Dhanush D Prabhu
committed
Added branch_and_bound Algorithm
1 parent 2c15b8c commit a6fb347

File tree

1 file changed

+122
-0
lines changed

1 file changed

+122
-0
lines changed
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
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, Tuple
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(
33+
node: Node, capacity: int, items: List[Item]
34+
) -> float:
35+
"""
36+
Calculate the upper bound of profit for a node using
37+
the fractional knapsack approach.
38+
"""
39+
if node.weight >= capacity:
40+
return 0.0
41+
42+
profit_bound = float(node.profit)
43+
total_weight = node.weight
44+
index = node.level + 1
45+
46+
while index < len(items) and total_weight + items[index].weight <= capacity:
47+
total_weight += items[index].weight
48+
profit_bound += items[index].value
49+
index += 1
50+
51+
if index < len(items):
52+
profit_bound += (
53+
(capacity - total_weight)
54+
* items[index].value
55+
/ items[index].weight
56+
)
57+
58+
return profit_bound
59+
60+
61+
def knapsack_branch_and_bound(
62+
capacity: int, weights: List[int], values: List[int]
63+
) -> int:
64+
"""
65+
Solve the 0/1 Knapsack problem using the Branch and Bound technique.
66+
67+
>>> knapsack_branch_and_bound(50, [10, 20, 30], [60, 100, 120])
68+
220
69+
"""
70+
items = [Item(weight=w, value=v) for w, v in zip(weights, values)]
71+
items.sort(key=lambda item: item.value / item.weight, reverse=True)
72+
73+
priority_queue: List[Tuple[float, Node]] = []
74+
75+
root = Node(level=-1, profit=0, weight=0, bound=0.0)
76+
root.bound = calculate_bound(root, capacity, items)
77+
78+
heapq.heappush(priority_queue, (-root.bound, root))
79+
max_profit = 0
80+
81+
while priority_queue:
82+
_, current = heapq.heappop(priority_queue)
83+
84+
if current.bound <= max_profit:
85+
continue
86+
87+
next_level = current.level + 1
88+
if next_level >= len(items):
89+
continue
90+
91+
# Include next item
92+
include_node = Node(
93+
level=next_level,
94+
profit=current.profit + items[next_level].value,
95+
weight=current.weight + items[next_level].weight,
96+
bound=0.0,
97+
)
98+
99+
if include_node.weight <= capacity:
100+
max_profit = max(max_profit, include_node.profit)
101+
102+
include_node.bound = calculate_bound(include_node, capacity, items)
103+
if include_node.bound > max_profit:
104+
heapq.heappush(
105+
priority_queue, (-include_node.bound, include_node)
106+
)
107+
108+
# Exclude next item
109+
exclude_node = Node(
110+
level=next_level,
111+
profit=current.profit,
112+
weight=current.weight,
113+
bound=0.0,
114+
)
115+
116+
exclude_node.bound = calculate_bound(exclude_node, capacity, items)
117+
if exclude_node.bound > max_profit:
118+
heapq.heappush(
119+
priority_queue, (-exclude_node.bound, exclude_node)
120+
)
121+
122+
return max_profit

0 commit comments

Comments
 (0)