-
Notifications
You must be signed in to change notification settings - Fork 1.5k
/
BranchAndBoundKnapsackSolver.cs
164 lines (140 loc) · 7.11 KB
/
BranchAndBoundKnapsackSolver.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
using System;
using System.Collections.Generic;
using System.Linq;
namespace Algorithms.Knapsack;
/// <summary>
/// Branch and bound Knapsack solver.
/// </summary>
/// <typeparam name="T">Type of items in knapsack.</typeparam>
public class BranchAndBoundKnapsackSolver<T>
{
/// <summary>
/// Returns the knapsack containing the items that maximize value while not exceeding weight capacity.
/// Construct a tree structure with total number of items + 1 levels, each node have two child nodes,
/// starting with a dummy item root, each following levels are associated with 1 items, construct the
/// tree in breadth first order to identify the optimal item set.
/// </summary>
/// <param name="items">All items to choose from.</param>
/// <param name="capacity">The maximum weight capacity of the knapsack to be filled.</param>
/// <param name="weightSelector">
/// A function that returns the value of the specified item
/// from the <paramref name="items">items</paramref> list.
/// </param>
/// <param name="valueSelector">
/// A function that returns the weight of the specified item
/// from the <paramref name="items">items</paramref> list.
/// </param>
/// <returns>
/// The array of items that provides the maximum value of the
/// knapsack without exceeding the specified weight <paramref name="capacity">capacity</paramref>.
/// </returns>
public T[] Solve(T[] items, int capacity, Func<T, int> weightSelector, Func<T, double> valueSelector)
{
// This is required for greedy approach in upper bound calculation to work.
items = items.OrderBy(i => valueSelector(i) / weightSelector(i)).ToArray();
// nodesQueue --> used to construct tree in breadth first order
Queue<BranchAndBoundNode> nodesQueue = new();
// maxCumulativeValue --> maximum value while not exceeding weight capacity.
var maxCumulativeValue = 0.0;
// starting node, associated with a temporary created dummy item
BranchAndBoundNode root = new(level: -1, taken: false);
// lastNodeOfOptimalPat --> last item in the optimal item sets identified by this algorithm
BranchAndBoundNode lastNodeOfOptimalPath = root;
nodesQueue.Enqueue(root);
while (nodesQueue.Count != 0)
{
// parent --> parent node which represents the previous item, may or may not be taken into the knapsack
BranchAndBoundNode parent = nodesQueue.Dequeue();
// IF it is the last level, branching cannot be performed
if (parent.Level == items.Length - 1)
{
continue;
}
// create a child node where the associated item is taken into the knapsack
var left = new BranchAndBoundNode(parent.Level + 1, true, parent);
// create a child node where the associated item is not taken into the knapsack
var right = new BranchAndBoundNode(parent.Level + 1, false, parent);
// Since the associated item on current level is taken for the first node,
// set the cumulative weight of first node to cumulative weight of parent node + weight of the associated item,
// set the cumulative value of first node to cumulative value of parent node + value of current level's item.
left.CumulativeWeight = parent.CumulativeWeight + weightSelector(items[left.Level]);
left.CumulativeValue = parent.CumulativeValue + valueSelector(items[left.Level]);
right.CumulativeWeight = parent.CumulativeWeight;
right.CumulativeValue = parent.CumulativeValue;
// IF cumulative weight is smaller than the weight capacity of the knapsack AND
// current cumulative value is larger then the current maxCumulativeValue, update the maxCumulativeValue
if (left.CumulativeWeight <= capacity && left.CumulativeValue > maxCumulativeValue)
{
maxCumulativeValue = left.CumulativeValue;
lastNodeOfOptimalPath = left;
}
left.UpperBound = ComputeUpperBound(left, items, capacity, weightSelector, valueSelector);
right.UpperBound = ComputeUpperBound(right, items, capacity, weightSelector, valueSelector);
// IF upperBound of this node is larger than maxCumulativeValue,
// the current path is still possible to reach or surpass the maximum value,
// add current node to nodesQueue so that nodes below it can be further explored
if (left.UpperBound > maxCumulativeValue && left.CumulativeWeight < capacity)
{
nodesQueue.Enqueue(left);
}
// Cumulative weight is the same as for parent node and < capacity
if (right.UpperBound > maxCumulativeValue)
{
nodesQueue.Enqueue(right);
}
}
return GetItemsFromPath(items, lastNodeOfOptimalPath);
}
// determine items taken based on the path
private static T[] GetItemsFromPath(T[] items, BranchAndBoundNode lastNodeOfPath)
{
List<T> takenItems = new();
// only bogus initial node has no parent
for (var current = lastNodeOfPath; current.Parent is not null; current = current.Parent)
{
if(current.IsTaken)
{
takenItems.Add(items[current.Level]);
}
}
return takenItems.ToArray();
}
/// <summary>
/// Returns the upper bound value of a given node.
/// </summary>
/// <param name="aNode">The given node.</param>
/// <param name="items">All items to choose from.</param>
/// <param name="capacity">The maximum weight capacity of the knapsack to be filled.</param>
/// <param name="weightSelector">
/// A function that returns the value of the specified item
/// from the <paramref name="items">items</paramref> list.
/// </param>
/// <param name="valueSelector">
/// A function that returns the weight of the specified item
/// from the <paramref name="items">items</paramref> list.
/// </param>
/// <returns>
/// upper bound value of the given <paramref name="aNode">node</paramref>.
/// </returns>
private static double ComputeUpperBound(BranchAndBoundNode aNode, T[] items, int capacity, Func<T, int> weightSelector, Func<T, double> valueSelector)
{
var upperBound = aNode.CumulativeValue;
var availableWeight = capacity - aNode.CumulativeWeight;
var nextLevel = aNode.Level + 1;
while (availableWeight > 0 && nextLevel < items.Length)
{
if (weightSelector(items[nextLevel]) <= availableWeight)
{
upperBound += valueSelector(items[nextLevel]);
availableWeight -= weightSelector(items[nextLevel]);
}
else
{
upperBound += valueSelector(items[nextLevel]) / weightSelector(items[nextLevel]) * availableWeight;
availableWeight = 0;
}
nextLevel++;
}
return upperBound;
}
}