0 1 背包问题就是一个整数规划问题。下面给出整数规划的求解步骤
- 活结点集合
- 选择一个分支节点拓展,如果是空的话 转向6步,
- 确定LP问题的最优解,如果这个问题无解返回第二步
- 对应的LP 问题的最优值满足剪枝条件返回第二步
- 当前的LP问题的最优值是整数的话成立, 如果不是整数的话,按照某个非整数分量生成节点的后代节点,成为活点加入到集合中回到第二步
- 当前得到的为最优的
对应到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;
}