假期 2020.01.20
题目描述
0-1背包问题一般描述为:给定n种物品和一个背包。物品i的重量是w(i),其价值为v(i),背包的容量为c。问应该如何选择装入背包的物品,使得装入背包中的物品的总价值最大?
注: 每个物品只能使用一次。
思路分析
这次分析01背包问题,采用回溯的方法实现。
回溯用法
简单的来说,回溯就是采用DFS然后加上一个复原的操作,即从初始状态出发,然后按照深度优先遍历的方法,根据约束条件进行搜索。当发现当前节点满足不了求解的条件的时候,就复原当前操作,尝试其他途径进行搜索。常言道:“能进则进,进不了则换,换不了则退”。
算法要点
- 解空间的大小
- 约束条件
- 结束条件
题目具体举例分析
假设4个物品放入一个容量为10的背包,求最大价值。并且value[ ] = { 6,3,5,4},weight[ ] = { 2,5,4,2};
- 最开始应该判断是否背包能够容纳全部的物品,若能够,直接输出全部物品的价值和与全部物品序号即可。
- 前一步不满足,则: 根据此问题的约束条件cw + weight [ i ] < total_weight,进行下一步的求解。此步求解过程中,我们需要进行一下操作:
if (cw + weight[i] <= total_size)//当前重量小于背包的最大容量
{
choice[i] = 1;
cw += weight[i];
cp += value[i];
Back_track(i + 1);
choice[i] = 0;
cw -= weight[i];//回溯
cp -= value[i];//回溯
}
- 当第二步操作之后,因为我们需要求解的是最优解,那么还需要一个判断函数,当物品 i 未放入时,剩下的物品全部放入时,是否会出现更加优的解,那么增加以下函数:
int Bount_size(int i)
{
int val = 0;
for (; i <= count_size; i++)//剩余全部物品价值总和
val += value[i];
return val + cp;
}
- 将返回值与当前的最优值比较,如果大于当前最优值,那么物品i不放入,直接放入下一个物品 i + 1,如下函数:
if (Bount_size(i + 1) > best_value) {//不装入物品i时,搜索右子树
choice[i] = 0;
Back_track(i + 1);
}
- 根据存储选择的物品序号输出方案即可。
代码解析
#include<iostream>
#include<algorithm>
using namespace std;
#define Max_size 100
int count_size, total_size;
double cw = 0, cp = 0, best_value = 0;//当前背包的物品重量,价值与当前最优值
int choice[Max_size];//暂时选入物品序号
int best[Max_size];//存储最终选入的物品序号
double value[Max_size], weight[Max_size];
void Re_search();//查找函数
void Back_track(int);//搜索函数
int Bount_size(int);//判断是否继续查找
int main()
{
int i,j = 1;
cout << "请输入背包的容纳最大重量:";
cin >> total_size;
cout << "请输入总物品个数: ";
cin >> count_size;
cout << "请输入个物品的重量与价值(用空格分开)" << endl;
for (i = 1; i <= count_size; i++)
cin >> weight[i] >> value[i];
Re_search();
return 0;
}
void Re_search()//查找函数
{
double sum_weight = 0, sum_value = 0;//所有物品的重量与价值
for (int i = 1; i <= count_size; i++){
sum_value += value[i];//计算总价值与总重量
sum_weight += weight[i];
}
if (sum_weight < total_size) {//当背包可以将全部物品容纳时
best_value = sum_value;
cout << "放入背包的最大价值是:" << best_value << endl;
cout << "全部物品均放入背包" << endl;
}
else{//不能全部容纳时否则
Back_track(1);//递归搜索函数
cout << "放入背包的最大价值是:" << best_value << endl;
cout << "放入背包的物品序号是:";
for (int i = 1; i <= count_size; i++){
if (best[i] == 1)//输出装入的物品序号
cout << i << " ";
}
cout << endl;
}
return;
}
void Back_track(int i)//搜索函数
{
if (i > count_size) {//最后一个物品也纳入计算后
for (i = 1; i <= count_size; i++)//更新方案
best[i] = choice[i];
best_value = cp;//更新最优值
return;
}
if (cw + weight[i] <= total_size)//当前重量小于背包的最大容量
{
choice[i] = 1;
cw += weight[i];
cp += value[i];
Back_track(i + 1);
choice[i] = 0;
cw -= weight[i];//回溯
cp -= value[i];//回溯
}
if (Bount_size(i + 1) > best_value) {//不装入物品i时,搜索右子树
choice[i] = 0;
Back_track(i + 1);
}
return;
}
int Bount_size(int i)
{
int val = 0;
for (; i <= count_size; i++)//剩余全部物品价值总和
val += value[i];
return val + cp;
}
运行结果
算法优化
优化函数部分double Bount_size(int i)
因为还可以借助余下容量来减少需要判断的物品是否应该算入最大值上界的计算中。
double Bount_size(int i)
{
double weight_left = total_size - cw;
double val = 0;
for (; i <= count_size && weight[i] < weight_left; i++)//剩余小于剩余容量物品价值总和
{
weight_left -= weight[i];
val += value[i];
}
if (i < count_size)//还剩下物品i时
val += value[i] / weight[i] * weight_left;
return val + cp;
}
更新后代码
#include<iostream>
#include<algorithm>
using namespace std;
#define Max_size 100
int count_size, total_size;
double cw = 0, cp = 0, best_value = 0;//当前背包的物品重量,价值与当前最优值
int choice[Max_size];//暂时选入物品序号
int best[Max_size];//存储最终选入的物品序号
double value[Max_size], weight[Max_size];
void Re_search();//查找函数
void Back_track(int);//搜索函数
double Bount_size(int);//判断是否继续查找
int main()
{
int i,j = 1;
cout << "请输入背包的容纳最大重量:";
cin >> total_size;
cout << "请输入总物品个数: ";
cin >> count_size;
cout << "请输入个物品的重量与价值(用空格分开)" << endl;
for (i = 1; i <= count_size; i++)
cin >> weight[i] >> value[i];
Re_search();
return 0;
}
void Re_search()//查找函数
{
double sum_weight = 0, sum_value = 0;//所有物品的重量与价值
for (int i = 1; i <= count_size; i++){
sum_value += value[i];//计算总价值与总重量
sum_weight += weight[i];
}
if (sum_weight < total_size) {//当背包可以将全部物品容纳时
best_value = sum_value;
cout << "放入背包的最大价值是:" << best_value << endl;
cout << "全部物品均放入背包" << endl;
}
else{//不能全部容纳时否则
Back_track(1);//递归搜索函数
cout << "放入背包的最大价值是:" << best_value << endl;
cout << "放入背包的物品序号是:";
for (int i = 1; i <= count_size; i++){
if (best[i] == 1)//输出装入的物品序号
cout << i << " ";
}
cout << endl;
}
return;
}
void Back_track(int i)//搜索函数
{
if (i > count_size) {//最后一个物品也纳入计算后
for (i = 1; i <= count_size; i++)
best[i] = choice[i];
best_value = cp;
return;
}
if (cw + weight[i] <= total_size)//当前重量小于背包的最大容量
{
choice[i] = 1;
cw += weight[i];
cp += value[i];
Back_track(i + 1);
choice[i] = 0;
cw -= weight[i];//回溯
cp -= value[i];//回溯
}
if (Bount_size(i + 1) > best_value) {//不装入物品i时,搜索右子树
choice[i] = 0;
Back_track(i + 1);
}
return;
}
double Bount_size(int i)
{
double weight_left = total_size - cw;
double val = 0;
for (; i <= count_size && weight[i] < weight_left; i++)//剩余小于剩余容量物品价值总和
{
weight_left -= weight[i];
val += value[i];
}
if (i < count_size)//还剩下物品i时
val += value[i] / weight[i] * weight_left;
return val + cp;
}