0/1背包问题 - 分枝定界(C++实现)

0/1背包问题 - 分枝定界(C++实现)

flyfish

分枝定界法是利用启发式搜索和边界条件 来高效地解决组合优化问题。它通过分枝将问题分解为多个子问题,再通过定界和剪枝来避免不必要的搜索,从而在合理时间内找到最优解。
在0/1背包问题中,分枝定界法通过以下步骤解决问题:

  1. 分枝(Branching)
    将问题分解为更小的子问题,这些子问题组成了一个搜索树。每个节点代表选择某个物品或不选择某个物品,分枝产生包含该物品的子问题和不包含该物品的子问题。
    每个节点代表一个子问题的解空间,子问题通过某种规则(例如选取一个变量进行分解)进行进一步划分。

  2. 定界(Bounding)
    计算当前子问题的上界和下界,以评估该子问题是否可能包含最优解。计算包含当前节点的最优可能解(上界),如果该上界低于当前已知的最优解,则剪枝。
    如果某个子问题的上界低于当前已知的最优解,则可以剪枝,即无需进一步探索该子问题。

  3. 剪枝(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)
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

二分掌柜的

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值