0 1 背包问题(回溯以及分枝定界)

0 1 背包问题就是一个整数规划问题。下面给出整数规划的求解步骤

  1. 活结点集合
  2. 选择一个分支节点拓展,如果是空的话 转向6步,
  3. 确定LP问题的最优解,如果这个问题无解返回第二步
  4. 对应的LP 问题的最优值满足剪枝条件返回第二步
  5. 当前的LP问题的最优值是整数的话成立, 如果不是整数的话,按照某个非整数分量生成节点的后代节点,成为活点加入到集合中回到第二步
  6. 当前得到的为最优的

对应到0 1 背包问题中 对应的LP问题就是可以装部分物品,如果是按照单位价值排序的话,此时的背包总价值应该是最大的,这个值就是价值上界,使用节点拓展的时候根据这个值来决定优先级来拓展,遇到不符合的就进行剪枝。因为拓展的是具有优先级的,因此是最有可能是最优解,直接拓展左子树即可,但是拓展右子树的时候先判断一下价值上界与当前的最优解的关系。(因为这个是按照单位价值降序排序的,很可能右子树的拓展是没有意义的)。

/**
 *   回溯 0 1背包问题
 */
#include <iostream>
using namespace std;
int x[10];
int v[10];
int bestx[10];
int current_weight;
int weight_all;
int value_all;
int current_value;
int w[10];
int n; //层次
int bestv;
int Bound(int i)
{
    
    
    // 先要做一个sort 用于将相对的价值高的放入其中
    int left_weight = weight_all - current_weight;
    int bound_value = current_value;
    while (i <= n && left_weight < w[i]) // 注意这里的条件是不能变化的
    {
    
    
        left_weight -= w[i];
        i++;
        bound_value += v[i];
    }
    if (i <= n)
    {
    
    
        bound_value += left_weight * (v[i] / w[i]);
    }
    return bound_value;
}
void backtrack(int t)
{
    
    
    /*
假设当前的最优解序列为 m , 现在有一个序列到达了叶子节点 为k 假设k<m 那么如果序列不同一定是分支不同, 分支不同的话因为使用了Bound()函数在这个函数中是可以确定会不会出现最优解。( 因为此时是按照v[i] /w[i]的价值降序排列的 在相同的容量中在装满的情况下价值是最大的)
*/
    if (t > n)
    {
    
     //边界的话
        for (int k = 1; k < n; k++)
        {
    
    
            bestx[k] = x[k];
        }
        value_all = current_value;
    }
    else
    {
    
    
        if (current_weight + w[t] < weight_all)
        {
    
    
            //可以装
            current_value += v[t];
            current_weight += w[t];
            x[t] = 1;
            backtrack(t + 1);
            current_value -= v[t];
            current_weight -= w[t];
        }
        int up = Bound(t + 1);
        if (up >= value_all)
        {
    
     
            x[t] = 0;
            backtrack(t + 1);
            // 这里其实是含有一个判断当前最优解与最优解的大小的 , 只有到第n+1层才能看到情况
        }
    }
}
int main()
{
    
    
    // init();
    //在一开始做一个排序
    backtrack(1); // 相当于第一个节点就在拓展
                  // output();
}

回溯法因为是深度优先遍历所以代码就显得十分简单。

#include <iostream>
#include <cstring>
#include <cmath>
#include <queue>
using namespace std;
#define N 10
bool bestx[N];
int w[N];
int v[N];
// (1)定义一个优先队列
// node 结构体 因为进队的时候是按照优先级排序
struct Node
{
    
    
    int cp;    //
    double up; // 价值上界
    int rw;    // 背包剩余容量
    int id;    //物品号
    bool x[N];
    Node()
    {
    
    
        memset(x, 0, sizeof(x));
    }
    Node(int _cp, double _up, int _rw, int _id)
    {
    
    
        cp = _cp;
        up = _up;
        rw = _rw;
        id = _id;
    }
};
//一个货物的数组
struct Goods
{
    
    
    int weight;
    int value;
} goods[N];
struct Object // 用于排序的
{
    
    
    int id;
    double d; //单位的重量价值
} S[N];
// 用于物品按照v[i]/w[i]排序
bool cmp(Object a1, Object a2)
{
    
    
    return a1.d > a2.d;
}
// 定义队列的优先级别
bool operator<(Node &a, Node &b)
{
    
    
    return a.up < b.up;
}

int bestp, W, n, sumw, sumv;
//bestp : 用于记录最优解 
//W: 最大的背包容量
//n 为物品的数量
//sumw : 所有的物品的总重量
//sumv : 为所有的物品的总价值
double Bound(Node tnode)
{
    
    
    double maxvalue = tnode.cp;
    int t = tnode.id;
    double left = tnode.rw;
    while (t <= n && w[t] <= left)
    {
    
    
        maxvalue += v[t];
        t++;
        left -= w[t];
    }
}
int priobfs()
{
    
    
    int t, tcp, tup, trw;        //当前的物品序号 t , 当前的装入的价值tcp, 价值上界tup , 当前的剩余容量trw
    priority_queue<Node> q;      // 优先队列
    q.push(Node(0, sumv, W, 1)); //根节点 加入队列
    while (!q.empty())
    {
    
    
        Node liveNode, lchild, rchild;
        liveNode = q.top(); //
        q.pop();
        t = liveNode.id;
        if (t > n || liveNode.rw == 0) // 是这么直接剪枝的
        {
    
    
            if (liveNode.cp >= bestp)
            {
    
    
                for (int i = 1; i <= n; i++)
                {
    
    
                    bestx[i] = liveNode.x[i];
                }
                bestp = liveNode.cp;
            }
            
           // if(t>n) return bestp;
            
            continue; // 因为可能是因为没有地方放置了
        }
        if (liveNode.up < bestp)
            continue; // 可以认为是加快结束循环  
                     // 下来拓展左节点
        tcp = liveNode.cp;
        trw = liveNode.rw;
        if (trw >= w[t])
        {
    
    
            lchild.cp = tcp + v[t];
            lchild.rw = trw - w[t];
            lchild.id = t + 1;
            tup = Bound(lchild);
            lchild = Node(lchild.cp, tup, lchild.rw, t + 1);
            for (int i = 1; i < t; i++)
            {
    
    
                lchild.x[i] = liveNode.x[i];
            }
            lchild.x[t] = true;
            if (lchild.cp > bestp)
            {
    
    
                bestp = lchild.cp;
            }
            q.push(lchild);
        }
        // 右节点
        rchild.cp = tcp;
        rchild.rw = trw;
        rchild.id = t + 1;
        tup = Bound(rchild);
        if (tup >= bestp)   // 因为如果是小于的话 是没有意义的 
        {
    
     // 满足条件
            rchild = Node(tcp, tup, trw, t + 1);
            for (int i = 1; i < t; i++)
            {
    
    
                rchild.x[i] = liveNode.x[i];
            }
            rchild.x[t] = false;
            q.push(rchild);
        }
    }
    return bestp; // 返回最优解的方法
}

int main()
{
    
    
 bestp=0;
 sumw=0;
 sumv=0;
 for(int i =1;i<=n;i++){
    
    
      // init 
      S[i-1].d=1.0*goods[i].value/goods[i].weight;

 }
 
 if(sumw<=w){
    
    
     
 }
sort(S,S+n,cmp);
for( int i =1;i<=n;i++){
    
    
  w[i]=goods[S[i-1].id].weight;
  v[i]=goods[S[i-1].id].value;
}
priobfs();
for( int i=0;i<=n;i++){
    
    
   if(bestx[i]==1){
    
    
    //S[i-1].id is  chosen 
   }
}
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_44724691/article/details/112425970