实验要求
1、问题描述
假设有一个能装入总体积为T的背包和n件体积分别为w1,w2,…wn的物品,能否从n件物品中挑选若干件恰好装满背包,即使w1+w2+…+wm=T,要求找出所有满足上述条件的解。
2、设计功能要求
例如:当T=10,各件物品的体积{1,8,4,3,5,2}时,可找到下列4组解:
(1,4,3,2)
(1,4,5)
(8,2)
(3,5,2)
3、实现提示
可利用回溯法的设计思想来解决背包问题。首先,将物品排成一列,然后,顺序选取物品装入背包,若已选取第i件物品后未满,则继续选取第i+1件,若该件物品“太大”不能装入,则弃之,继续选取下一件,直至背包装满为止。
如果在剩余的物品中找不到合适的物品以填满背包,则说明“刚刚”装入的物品“不合适”,应将它取出“弃之一边”,继续再从“它之后”的物品中选取,如此重复,直到求得满足条件的解,或者无解。由于回溯求解的规则是“后进先出”,自然要用到“栈”。
进一步考虑:如果每件物品都有体积和价值,背包又有大小限制,求解背包中存放物品总价值最大的问题解---最优解或近似最优解。
算法难点
如何选取物品,且当已经找到一组解的时候去继续寻找下一组解。
解决思路及算法
首先,根据提示,我们需要先定义栈和栈的基本操作:初始化 initstack(seqstack &s),入栈push(seqstack&s,int x),出栈pop(seqstack &s)。然后我们要选取物品,物品可以用数组定义w[num],这样的话可以得到物品的编号i,物品的重量w[i]。当体积T大于选中的物品时,将该物品入栈,然后用总体积T减去该物体重量,若剩余体积大于0,则进一步将物体入栈,若等于0,则可以输出一组解,否则,将栈顶元素出栈。循环进行上述过程,知道栈空且最后一次出栈物品的编号是num。
主要说一下knap函数的功能实现:
定义k作为标识记录当前入栈物品的编号
S1:当第i物品体积小于T时,将该物品入栈,同时减去该物品体积(T-w[i]),k++;
S2:若体积T大于0或k小于总物品个数,循环进行S2;
S3:当T为0时,可输入一组解;
S4:将栈顶元素出栈,同时用k记录该物品编号,再将栈中该物品位置的体积置为0,同时加上该物品的体积(T+w[i]),k++;
S5:循环进行S2,S3,S4直至k为总物品个数且栈内为空。
接下来,我进一步思考了0-1背包问题。这就是各物品“选”与“不选”的问题。定义结构体item,设items[i].v是价值,item[i].w是重量;定义二位数组C[N+1][W+1],C[i][w]表示前i个物品装入容量为w的背包时总价值的最大值。则C[i][w]为下述两者中的较大者:
1、C[i-1][w-物品i的重量]+物品i的价值//当前选择物品i的情况
2、C[i-1][w]//当前不选择物品i的情况
测试样例
反思启发
在思考问题的初期,在什么条件下让物品出栈以及什么情况下连续出栈这个方面的问题一直困扰着我,使得在代码调试过程中也出了不少差错,这都是对堆栈这一情形不熟悉所导致。通过设计此算法,我进一步熟悉掌握堆栈的用法以及回溯算法,这种数据结构思想在解题的过程中是十分重要的。
代码附录
#include<stdio.h>
#define maxsize 20
int ans=0;//解法的个数
typedef struct
{
int size[maxsize];
int top;
}seqstack;
void initstack(seqstack &s)
{
s.top=0;
int i;
for(i=0;i<maxsize;i++)
s.size[i]=0;
}
void push(seqstack &s,int x)
{
if(s.top+1==maxsize) printf("数组上溢。\n");
else
{
s.size[s.top++]=x;
}
}
void pop(seqstack &s)
{
if(s.top==-1) printf("数组下溢。\n");
else s.top--;
}
void knap(seqstack &s,int num,int w[],int T)
{
int k=0,i=0,j=1;
do
{
while(T>0&&k<=num)
{
if(T>=w[k])
{
push(s,k);
T-=w[k];
}
k++;
}
if(T==0)
{
printf("\n第%d种挑选方法(",j );
for(i=0;i<s.top;i++)
{
printf("%d ",w[s.size[i]]);
}
j++;
printf(")\n");
}
pop(s);//出栈操作
k=s.size[s.top];
s.size[s.top]=0;
T+=w[k];
k++;
}while(!(s.top==0&&k==num));
}
int main()
{
seqstacks;
initstack(s);
intT;//背包体积
printf("请输入背包体积:");
scanf("%d",&T);
intnum;//背包个数
printf("请输入背包个数:");
scanf("%d",&num);
intw[maxsize];//背包内物体体积
printf("请输入背包内各个物体体积:\n");
for(int i=0;i<num;i++)
{
scanf("%d",&w[i]);
}
knap(s,num,w,T);
return0;
}
0-1背包问题:
#include<iostream>
#include<vector>
#include<algorithm>
#define NMAX 105
#define WMAX 10005
#define DIAGONAL 1
#define TOP 0
using namespace std;
struct Item{
int value,weight;
};
int N,W;
Item items[NMAX+1];
int C[NMAX+1][WMAX+1],G[NMAX+1][WMAX+1];
void compute(int &maxValue, vector<int> &selection)
{
for(int w=0;w<W;w++)
{
C[0][w]=0;
G[0][w]=DIAGONAL;//选择物品i
}
for(int i=1;i<=N;i++) C[i][0]=0;
for(int i=1;i<=N;i++)
{
for(int w=1;w<W;w++)
{
C[i][w]=C[i-1][w];
G[i][w]=TOP;//不选择物品i
if(items[i].weight>w) continue;
if(items[i].value+C[i-1][w-items[i].weight]>C[i-1][w])
{
C[i][w]=items[i].value+C[i-1][w-items[i].weight];
G[i][w]=DIAGONAL;//不能选择物品
}
}
}
maxValue=C[N][W];
selection.clear();
for(int i=N,w=W;i>=1;i--)
{
if(G[i][w]==DIAGONAL)
{
selection.push_back(i);
w-=items[i].weight;
}
}
reverse(selection.begin(),selection.end());
}
void input()
{
cin>>N>>W;
for(int i=1;i<=N;i++)
{
cin>>items[i].value>>items[i].weight;
}
}
int main()
{
int maxValue;
vector<int> selection;
input();
compute(maxValue,selection);
cout<<maxValue<<endl;
return 0;
}