0/1背包问题 - 分枝定界(C++实现)
flyfish
分枝定界法是利用启发式搜索和边界条件 来高效地解决组合优化问题。它通过分枝将问题分解为多个子问题,再通过定界和剪枝来避免不必要的搜索,从而在合理时间内找到最优解。
在0/1背包问题中,分枝定界法通过以下步骤解决问题:
-
分枝(Branching) :
将问题分解为更小的子问题,这些子问题组成了一个搜索树。每个节点代表选择某个物品或不选择某个物品,分枝产生包含该物品的子问题和不包含该物品的子问题。
每个节点代表一个子问题的解空间,子问题通过某种规则(例如选取一个变量进行分解)进行进一步划分。 -
定界(Bounding) :
计算当前子问题的上界和下界,以评估该子问题是否可能包含最优解。计算包含当前节点的最优可能解(上界),如果该上界低于当前已知的最优解,则剪枝。
如果某个子问题的上界低于当前已知的最优解,则可以剪枝,即无需进一步探索该子问题。 -
剪枝(Pruning) :
通过定界条件(上界和下界)来排除不可能包含最优解的子问题,从而减少搜索空间。移除不可能包含最优解的子问题,只保留有希望包含最优解的子问题进行进一步探索。
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
// 物品类,表示每个物品的重量、价值和原始索引
class Item {
public:
int weight; // 物品重量
int value; // 物品价值
int index; // 原始索引
Item(int w, int v, int i) : weight(w), value(v), index(i) {}
};
// 节点类,表示分枝定界树中的每个节点
class Node {
public:
int level; // 当前节点在树中的层级
int profit; // 当前节点对应的利润
int weight; // 当前节点对应的重量
float bound; // 当前节点的边界值
std::vector<bool> include; // 表示是否包含物品的向量
Node(int l, int p, int w, float b, std::vector<bool> inc)
: level(l), profit(p), weight(w), bound(b), include(inc) {}
};
// 比较节点的边界值,用于优先队列
class Compare {
public:
bool operator()(Node const& n1, Node const& n2) {
return n1.bound < n2.bound;
}
};
// 计算节点的边界值
float CalculateBound(Node u, int n, int W, std::vector<Item>& items) {
if (u.weight >= W) return 0; // 如果重量超过背包容量,边界值为0
float profit_bound = u.profit;
int j = u.level + 1;
int totweight = u.weight;
// 计算节点的边界值
while ((j < n) && (totweight + items[j].weight <= W)) {
totweight += items[j].weight;
profit_bound += items[j].value;
j++;
}
// 如果还有剩余的容量,计算潜在的最大值
if (j < n) {
profit_bound += (W - totweight) * items[j].value / (float)items[j].weight;
}
return profit_bound;
}
// 分枝定界算法求解0/1背包问题
int Knapsack(int W, std::vector<Item>& items, std::vector<bool>& best_set) {
// 按照单位价值从高到低排序物品
std::sort(items.begin(), items.end(), [](Item const& a, Item const& b) {
return (float)a.value / a.weight > (float)b.value / b.weight;
});
std::priority_queue<Node, std::vector<Node>, Compare> Q;
std::vector<bool> empty_set(items.size(), false);
Node u(-1, 0, 0, 0.0f, empty_set); // 初始化根节点
Node v(0, 0, 0, 0.0f, empty_set);
u.bound = CalculateBound(u, items.size(), W, items); // 计算根节点的边界值
Q.push(u); // 将根节点加入优先队列
int maxProfit = 0;
// 分枝定界主循环
while (!Q.empty()) {
u = Q.top(); // 取出边界值最大的节点
Q.pop();
if (u.bound > maxProfit) {
v.level = u.level + 1;
// 考虑包含当前物品的节点
v.weight = u.weight + items[v.level].weight;
v.profit = u.profit + items[v.level].value;
v.include = u.include;
if (v.level < items.size()) v.include[v.level] = true;
if (v.weight <= W && v.profit > maxProfit) {
maxProfit = v.profit;
best_set = v.include; // 更新最优解
}
v.bound = CalculateBound(v, items.size(), W, items);
if (v.bound > maxProfit) {
Q.push(v); // 将包含当前物品的节点加入优先队列
}
// 考虑不包含当前物品的节点
v.weight = u.weight;
v.profit = u.profit;
v.include = u.include;
if (v.level < items.size()) v.include[v.level] = false;
v.bound = CalculateBound(v, items.size(), W, items);
if (v.bound > maxProfit) {
Q.push(v); // 将不包含当前物品的节点加入优先队列
}
}
}
return maxProfit; // 返回最大利润
}
int main() {
int W = 10; // 背包容量
std::vector<Item> items = {Item(3, 30, 1), Item(5, 20, 2), Item(4, 10, 3), Item(2, 40, 4)}; // 物品列表
std::vector<bool> best_set(items.size(), false);
int maxProfit = Knapsack(W, items, best_set); // 求解最大利润
std::cout << "Maximum profit is " << maxProfit << std::endl;
std::cout << "Items included in the knapsack are:" << std::endl;
for (size_t i = 0; i < items.size(); ++i) {
if (best_set[i]) {
std::cout << "Item " << items[i].index << " (Weight: " << items[i].weight
<< ", Value: " << items[i].value << ", Original Index: " << items[i].index << ")" << std::endl;
}
}
return 0;
}
输出
Maximum profit is 90
Items included in the knapsack are:
Item 4 (Weight: 2, Value: 40, Original Index: 4)
Item 1 (Weight: 3, Value: 30, Original Index: 1)
Item 2 (Weight: 5, Value: 20, Original Index: 2)