0-1背包问题(回溯法解决)
给定一个物品集合s={1,2,3,…,n},物品i的重量是wi,其价值是vi,背包的容量为W,即最大载重量不超过W。在限定的总重量W内,我们如何选择物品,才能使得物品的总价值最大。
输入
第一个数据是背包的容量为c(1≤c≤1500),第二个数据是物品的数量为n(1≤n≤50)。接下来n行是物品i的重量是wi,其价值为vi。所有的数据全部为整数,且保证输入数据中物品的总重量大于背包的容量。
输出:对每组测试数据,输出装入背包中物品的最大价值。
令cw(i)表示目前搜索到第i层已经装入背包的物品总重量,即部分解(x1, x2 , …, xi)的重量:
对于左子树, xi =1 ,其约束函数为:
若constraint(i)>W,则停止搜索左子树,否则继续搜索。
对于右子树,为了提高搜索效率,采用限界函数Bound(i)剪枝。
令cv(i)表示目前到第i层结点已经装入背包的物品价值:
令r(i)表示剩余物品的总价值:
则限界函数Bound(i)为:
假设当前最优值为bestv,若Bound(i)<bestv,则停止搜索第i层结点及其子树,否则继续搜索。
显然r(i)越小, Bound(i)越小,剪掉的分支就越多(在高层剪枝,剪掉的分支越多。从而能加快搜索速度)。
为了构造更小的r(i) ,将物品以单位重量价值比di=vi/wi递减的顺序进行排列(贪心策略):
d1≥d2≥… ≥dn
对于第i层,背包的剩余容量为W-cw(i),采用贪心算法把剩余的物品放进背包。
为什么没有按照重量进行排序呢?
0-1背包问题的目标是:
在不超重的前提下计算背包内物品的最大价值
因此,贪心策略是:
按照单位价值由大到小进行处理
物品的价值会对限界函数产生影响
物品的重量会对约束函数产生影响
由于根据物品价值制定贪心策略
所以,通过单位价值排序,加速剪枝
#include<iostream>
#include<algorithm>
using namespace std;
#define NUM 100
int c; //背包的容量
int n; //物品的数量
int cw; //当前背包内物品的重量
int cv; //当前背包内物品的总价值
int bestv; //当前最优价值
//描述每个物品的数据结构
struct Object
{
public:
int w; //物品的重量
int v; //物品的价值
double d; //物品的单位价值
public:
double getd()
{
return d;
}
}; //物品数组
Object Q[NUM];
void backtrack(int i);
int Bound(int i);
bool cmp(Object, Object);
//物品的单位价值重量比是在输入数据时计算的
int main()
{
cin >> c >> n;
for (int i = 0; i < n; i++)
{
//物品的单位价值重量比是在输入数据时计算的
scanf_s("%d%d", &Q[i].w, &Q[i].v);
Q[i].d = 1.0 * Q[i].v / Q[i].w;
}
sort(Q, Q + n, cmp);
backtrack(1);
cout << bestv;
}
//以物品单位价值重量比递减排序的因子:
bool cmp(Object a, Object b)
{
if (a.d>= b.d)
return true;
else
return false;
}
void backtrack(int i)
{
//到达叶子节点时更新最优值
if (i + 1 > n)
{
bestv = cv;
return;
}
//进入左子树搜索
if (cw + Q[i].w <= c)
{
cw += Q[i].w;
cv += Q[i].v;
backtrack(i + 1);
cw -= Q[i].w;
cv -= Q[i].v;
}
//进入右子树搜索
if (Bound(i + 1) > bestv)
backtrack(i + 1);
}
int Bound(int i)
{
int cleft = c - cw; //背包剩余的容量
int b = cv; //上界
//尽量装满背包
while (i < n && Q[i].w <= cleft)
{
cleft -= Q[i].w;
b += Q[i].v;
i++;
}
//剩余的部分空间也装满
if (i < n)
b += 1.0 * cleft * Q[i].v / Q[i].w;
return b;
}