问题描述:
给定n种物品和一背包。物品i的重量是wi,其价值为vi,背包的容量为W。一个物品要么全部装入背包,要么全部不装入背包,不允许部分装入。装入背包的物品的总重量不超过背包的容量。问应如何选择装入背包的物品,使得装入背包中的物品总价值最大?
定义问题的解空间
- 解的形式
(
x
1
,
x
2
,
…
,
x
n
)
(x1,x2,…,xn)
(x1,x2,…,xn),其中
x
i
=
0
xi=0
xi=0或
1
1
1
- x i = 0 x_i=0 xi=0:第 i i i个物品不装入背包
- x i = 1 x_i=1 xi=1:第 i i i个物品装入背包
约束条件:
- w i ≤ c ′ w_i≤c' wi≤c′( c ′ c' c′为包的剩余容量),也就是 ∑ i = 1 n w i x i ≤ W \sum_{i = 1}^{n} w_ix_i≤ W ∑i=1nwixi≤W
限界条件:
- c p > b e s t p cp>bestp cp>bestp (在第 n − 1 n-1 n−1层限界)目标: b e s t p bestp bestp最大,初值 b e s t p = 0 bestp=0 bestp=0
-
c
p
+
r
p
>
b
e
s
t
p
cp+rp>bestp
cp+rp>bestp
- r p rp rp:剩余物品的总价值
- b e s t p bestp bestp:当前已经找到的最优解的价值
搜索过程:
- 以
n
=
4
,
W
=
7
,
w
=
(
3
,
5
,
2
,
1
)
,
v
=
(
9
,
10
,
7
,
4
)
n=4,W=7,w=(3,5,2,1),v=(9,10,7,4)
n=4,W=7,w=(3,5,2,1),v=(9,10,7,4) 为例展示搜索过程
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
// 物品的个数
int n = 0;
// 最大能装载的物品个数
int max_w = 0;
// 最大价值
int max_v = 0;
// 物品重量序列
int* w = NULL;
// 物品价值序列
int* v = NULL;
// 标记序列,标记的是当前搜索的路径
int* mask = NULL;
// 结果序列,得到的是物品装与不装
int* res = NULL;
// 显示标记数组
void display() {
for (int i = 0; i < n; i++) {
cout << res[i] << " ";
}
cout << endl;
}
/**
* @param flag 当前物品是装还是不装
* @param times 当前迭代到的层数
* @param weight 当前已经装载的重量
* @param value 当前获得的价值
*/
void TraceBack(bool flag, int times, int weight, int value) {
// 到当前结点了,更新标记数组,是否装
mask[times] = flag ? 1 : 0;
// 递归的层数大于n的时候,直接返回
if (times > n) {
return;
}
// 递归到叶子结点时,进行比较,若最大价值大,则更新,并拷贝标记数组
if (times == n && value > max_v) {
max_v = value;
memcpy(res, mask, sizeof(int) * n);
cout << "叶节点" << endl;
display();
cout << "--------------------------------------------" << endl;
return;
}
// 如果当前不装
if (flag == false) {
// 下一个结点装
TraceBack(true, times + 1, weight, value);
// 下一结点不装
TraceBack(false, times + 1, weight, value);
return;
}
// 当前结点装,那能否装得下,装不下的直接剪枝了
if (w[times] + weight <= max_w) {
// 装下了,下一个结点装
TraceBack(true, times + 1, w[times] + weight, v[times] + value);
// 下一个结点不装
TraceBack(false, times + 1, w[times] + weight, v[times] + value);
return ;
}
}
// 打印解决方案
void solve() {
cout << "**************************************************" << endl;
cout << "01背包装载方案如下: " << endl;
for(int i = 0; i < n; i++ ) {
if(res[i] == 1 ) {
cout << "\t第" << i + 1<< "个装载" << endl;
}
}
cout << endl;
}
int main() {
printf("请输入背包的总容量:");
scanf("%d", &max_w);
printf("请输入物品的数量:");
scanf("%d", &n);
w = new int[n];
v = new int[n];
mask = new int[n];
res = new int[n];
memset(w, 0, sizeof(int) * n);
memset(mask, 0, sizeof(int) * n);
memset(res, 0, sizeof(int) * n);
memset(v, 0, sizeof(int) * n);
for (int i = 0; i < n; i++) {
scanf("%d%d", &w[i], &v[i]);
}
if (max_w >= w[0]) {
mask[0] = 1;
// 下一个装
TraceBack(true, 1, w[0], v[0]);
// 下一个不装
TraceBack(false, 1, w[0], v[0]);
}
// 第一个不装
mask[0] = 0;
// 第二个装
TraceBack(true, 1, 0, 0);
// 第二个不装
TraceBack(false, 1, 0, 0);
cout << "best value is " << max_v << endl;
cout << "标记数组为 ";
display();
solve();
delete w;
delete v;
delete res;
delete mask;
return 0;
}
/*
7
4
3 9
5 10
2 7
3 4
*/