From a6fb347f0f3f5c4803f87aee647cf78de7223b41 Mon Sep 17 00:00:00 2001 From: Dhanush D Prabhu <=itsdevdhanush> Date: Sun, 28 Dec 2025 12:55:02 +0530 Subject: [PATCH 1/4] Added branch_and_bound Algorithm --- knapsack/knapsack_branch_and_bound.py | 122 ++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 knapsack/knapsack_branch_and_bound.py diff --git a/knapsack/knapsack_branch_and_bound.py b/knapsack/knapsack_branch_and_bound.py new file mode 100644 index 000000000000..770cd34b0ddf --- /dev/null +++ b/knapsack/knapsack_branch_and_bound.py @@ -0,0 +1,122 @@ +""" +Branch and Bound solution for the 0/1 Knapsack problem. + +This implementation uses a best-first search strategy and prunes +non-promising branches using an upper bound calculated via the +fractional knapsack (greedy) approach. + +References: +https://en.wikipedia.org/wiki/Branch_and_bound +https://en.wikipedia.org/wiki/Knapsack_problem +""" + +from dataclasses import dataclass +from typing import List, Tuple +import heapq + + +@dataclass +class Item: + weight: int + value: int + + +@dataclass +class Node: + level: int + profit: int + weight: int + bound: float + + +def calculate_bound( + node: Node, capacity: int, items: List[Item] +) -> float: + """ + Calculate the upper bound of profit for a node using + the fractional knapsack approach. + """ + if node.weight >= capacity: + return 0.0 + + profit_bound = float(node.profit) + total_weight = node.weight + index = node.level + 1 + + while index < len(items) and total_weight + items[index].weight <= capacity: + total_weight += items[index].weight + profit_bound += items[index].value + index += 1 + + if index < len(items): + profit_bound += ( + (capacity - total_weight) + * items[index].value + / items[index].weight + ) + + return profit_bound + + +def knapsack_branch_and_bound( + capacity: int, weights: List[int], values: List[int] +) -> int: + """ + Solve the 0/1 Knapsack problem using the Branch and Bound technique. + + >>> knapsack_branch_and_bound(50, [10, 20, 30], [60, 100, 120]) + 220 + """ + items = [Item(weight=w, value=v) for w, v in zip(weights, values)] + items.sort(key=lambda item: item.value / item.weight, reverse=True) + + priority_queue: List[Tuple[float, Node]] = [] + + root = Node(level=-1, profit=0, weight=0, bound=0.0) + root.bound = calculate_bound(root, capacity, items) + + heapq.heappush(priority_queue, (-root.bound, root)) + max_profit = 0 + + while priority_queue: + _, current = heapq.heappop(priority_queue) + + if current.bound <= max_profit: + continue + + next_level = current.level + 1 + if next_level >= len(items): + continue + + # Include next item + include_node = Node( + level=next_level, + profit=current.profit + items[next_level].value, + weight=current.weight + items[next_level].weight, + bound=0.0, + ) + + if include_node.weight <= capacity: + max_profit = max(max_profit, include_node.profit) + + include_node.bound = calculate_bound(include_node, capacity, items) + if include_node.bound > max_profit: + heapq.heappush( + priority_queue, (-include_node.bound, include_node) + ) + + # Exclude next item + exclude_node = Node( + level=next_level, + profit=current.profit, + weight=current.weight, + bound=0.0, + ) + + exclude_node.bound = calculate_bound(exclude_node, capacity, items) + if exclude_node.bound > max_profit: + heapq.heappush( + priority_queue, (-exclude_node.bound, exclude_node) + ) + + return max_profit From d35fe4ddd7aa5e94d0ab6f743eb64b0a5570545c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 28 Dec 2025 07:29:23 +0000 Subject: [PATCH 2/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- knapsack/knapsack_branch_and_bound.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/knapsack/knapsack_branch_and_bound.py b/knapsack/knapsack_branch_and_bound.py index 770cd34b0ddf..7566dfa8a7d2 100644 --- a/knapsack/knapsack_branch_and_bound.py +++ b/knapsack/knapsack_branch_and_bound.py @@ -29,9 +29,7 @@ class Node: bound: float -def calculate_bound( - node: Node, capacity: int, items: List[Item] -) -> float: +def calculate_bound(node: Node, capacity: int, items: List[Item]) -> float: """ Calculate the upper bound of profit for a node using the fractional knapsack approach. @@ -50,9 +48,7 @@ def calculate_bound( if index < len(items): profit_bound += ( - (capacity - total_weight) - * items[index].value - / items[index].weight + (capacity - total_weight) * items[index].value / items[index].weight ) return profit_bound @@ -101,9 +97,7 @@ def knapsack_branch_and_bound( include_node.bound = calculate_bound(include_node, capacity, items) if include_node.bound > max_profit: - heapq.heappush( - priority_queue, (-include_node.bound, include_node) - ) + heapq.heappush(priority_queue, (-include_node.bound, include_node)) # Exclude next item exclude_node = Node( @@ -115,8 +109,6 @@ def knapsack_branch_and_bound( exclude_node.bound = calculate_bound(exclude_node, capacity, items) if exclude_node.bound > max_profit: - heapq.heappush( - priority_queue, (-exclude_node.bound, exclude_node) - ) + heapq.heappush(priority_queue, (-exclude_node.bound, exclude_node)) return max_profit From ccef627419ca89032c38128876c30c6a0c23083a Mon Sep 17 00:00:00 2001 From: Dhanush D Prabhu Date: Sun, 28 Dec 2025 13:02:32 +0530 Subject: [PATCH 3/4] Update knapsack_branch_and_bound.py --- knapsack/knapsack_branch_and_bound.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/knapsack/knapsack_branch_and_bound.py b/knapsack/knapsack_branch_and_bound.py index 7566dfa8a7d2..6c00bfa87211 100644 --- a/knapsack/knapsack_branch_and_bound.py +++ b/knapsack/knapsack_branch_and_bound.py @@ -10,9 +10,8 @@ https://en.wikipedia.org/wiki/Knapsack_problem """ -from dataclasses import dataclass -from typing import List, Tuple import heapq +from dataclasses import dataclass @dataclass @@ -29,7 +28,7 @@ class Node: bound: float -def calculate_bound(node: Node, capacity: int, items: List[Item]) -> float: +def calculate_bound(node: Node, capacity: int, items: list[Item]) -> float: """ Calculate the upper bound of profit for a node using the fractional knapsack approach. @@ -48,14 +47,16 @@ def calculate_bound(node: Node, capacity: int, items: List[Item]) -> float: if index < len(items): profit_bound += ( - (capacity - total_weight) * items[index].value / items[index].weight + (capacity - total_weight) + * items[index].value + / items[index].weight ) return profit_bound def knapsack_branch_and_bound( - capacity: int, weights: List[int], values: List[int] + capacity: int, weights: list[int], values: list[int] ) -> int: """ Solve the 0/1 Knapsack problem using the Branch and Bound technique. @@ -66,7 +67,7 @@ def knapsack_branch_and_bound( items = [Item(weight=w, value=v) for w, v in zip(weights, values)] items.sort(key=lambda item: item.value / item.weight, reverse=True) - priority_queue: List[Tuple[float, Node]] = [] + priority_queue: list[tuple[float, Node]] = [] root = Node(level=-1, profit=0, weight=0, bound=0.0) root.bound = calculate_bound(root, capacity, items) @@ -97,7 +98,9 @@ def knapsack_branch_and_bound( include_node.bound = calculate_bound(include_node, capacity, items) if include_node.bound > max_profit: - heapq.heappush(priority_queue, (-include_node.bound, include_node)) + heapq.heappush( + priority_queue, (-include_node.bound, include_node) + ) # Exclude next item exclude_node = Node( @@ -109,6 +112,8 @@ def knapsack_branch_and_bound( exclude_node.bound = calculate_bound(exclude_node, capacity, items) if exclude_node.bound > max_profit: - heapq.heappush(priority_queue, (-exclude_node.bound, exclude_node)) + heapq.heappush( + priority_queue, (-exclude_node.bound, exclude_node) + ) return max_profit From 1d1296dc1fcf7d73f49b7bdd3cc7cfb62ac471b1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 28 Dec 2025 07:32:52 +0000 Subject: [PATCH 4/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- knapsack/knapsack_branch_and_bound.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/knapsack/knapsack_branch_and_bound.py b/knapsack/knapsack_branch_and_bound.py index 6c00bfa87211..7c2a530d0863 100644 --- a/knapsack/knapsack_branch_and_bound.py +++ b/knapsack/knapsack_branch_and_bound.py @@ -47,9 +47,7 @@ def calculate_bound(node: Node, capacity: int, items: list[Item]) -> float: if index < len(items): profit_bound += ( - (capacity - total_weight) - * items[index].value - / items[index].weight + (capacity - total_weight) * items[index].value / items[index].weight ) return profit_bound @@ -98,9 +96,7 @@ def knapsack_branch_and_bound( include_node.bound = calculate_bound(include_node, capacity, items) if include_node.bound > max_profit: - heapq.heappush( - priority_queue, (-include_node.bound, include_node) - ) + heapq.heappush(priority_queue, (-include_node.bound, include_node)) # Exclude next item exclude_node = Node( @@ -112,8 +108,6 @@ def knapsack_branch_and_bound( exclude_node.bound = calculate_bound(exclude_node, capacity, items) if exclude_node.bound > max_profit: - heapq.heappush( - priority_queue, (-exclude_node.bound, exclude_node) - ) + heapq.heappush(priority_queue, (-exclude_node.bound, exclude_node)) return max_profit