最近刚完成了算法课程设计,题目是用多种解法解决01背包问题,经过一番探索,终于成功的用四种方法完成了本次实验,下面记录分享一下成果:
首先解释下什么是01背包问题:给定一组共n个物品,每种物品都有自己的重量wi, i=1~n和价值vi, i=1~n,在限定的总重量(背包的容量C)内,如何选择才能使得选择物品的总价值之和最高。选择最优的物品子集放置于给定背中,最优子集对应n元解向量(x1,…xn), xi∈{0或1},因此命名为0-1背包问题。
输入数据格式如下:
第一行C(背包容量)和n(物品个数);
接下来n行:wi(第i件物品的重量,1<=i<=n)和vi(第i件物品的价值,1<=i<=n)。
输出数据格式如下:
第一行背包物品的最大价值和;
接下来n行:i(物品编号,表示第i件物品)和xi(该件物品选或不选的0/1分量)。
采用文件输入输出方式测试数据。
以下是4种解决方法的代码(已附上详细注释):
1.动态规划法:
#include<stdio.h>
#include "stdlib.h"
#include "time.h"
int c,n;
int w[5100],v[5100],f[61000],x[5100][61000]; //f(i)表示背包容量为i的最优值,x(i)(j)表示背包容量为j时第i件物品是否放入(1/0)
int main(){
//计算运行时间
clock_t start, finish; /*精确到ms(毫秒)级的时间*/
double duration; /*测量一个事件持续的时间*/
start = clock();
FILE *fp1,*fp2;
fp1=fopen("in.txt","r");
fp2=fopen("动态规划out.txt","w");
int num=1;
while(!feof(fp1)){
fscanf(fp1,"%d%d",&c,&n);
int i,j,k;
for(i=1;i<=n;i++){
fscanf(fp1,"%d%d",&w[i],&v[i]);
for(j=0;j<=c;j++)
x[i][j]=0; //初始化,一开始每件物品都未放入
}
for(i=0;i<=c;i++){
f[i]=0; //初始化,一开始背包为空
}
for(i=1;i<=n;i++){
for(j=c;j>=w[i];j--){
if(f[j]<f[j-w[i]]+v[i]){ //背包容量为j时,若第i件物品放入比不放人价值大,则放入,更新f(j)的值
f[j]=f[j-w[i]]+v[i];
x[i][j]=1;
for(k=1;k<i;k++) //用前i-1件物品放入与否的情况更新当前背包情况
x[k][j]=x[k][j-w[i]];
}
}
}
fprintf(fp2,"第%d组数据:\n",num++);
fprintf(fp2,"%d\n",f[c]); //背包容量为c时的最优值
for(i=1;i<=n;i++)
fprintf(fp2,"%d %d\n",i,x[i][c]); //背包容量为c时每件物品放入与否
}
finish = clock();
duration = (double)(finish - start) / CLOCKS_PER_SEC;
fprintf( fp2,"%f seconds\n", duration ); /*此duration单位为秒*/
fclose(fp1);
fclose(fp2);
}
2.贪心法:
#include<stdio.h>
#include "stdlib.h"
#include "time.h"
int c,n,w[5100],v[5100],x[5100];
struct A{
double avg; //物品单位重量价值
int index; //物品下标
}a[5100];
void exchange(A &x1,A &x2){ //交换两结构体变量值
double temp1=x1.avg;
x1.avg=x2.avg;
x2.avg=temp1;
int temp2=x1.index;
x1.index=x2.index;
x2.index=temp2;
}
int main(){
clock_t start, finish; /*精确到ms(毫秒)级的时间*/
double duration; /*测量一个事件持续的时间*/
start = clock();
FILE *fp1,*fp2;
fp1=fopen("in.txt","r");
fp2=fopen("贪心法out.txt","w");
int num=1;
while(!feof(fp1)){
fscanf(fp1,"%d%d",&c,&n);
int i,j;
for(i=1;i<=n;i++){
fscanf(fp1,"%d%d",&w[i],&v[i]);
a[i].avg=v[i]*1.0/w[i];
a[i].index=i;
x[i]=0;
}
for(i=n;i>0;i--){ //将物品按单位重量价值降序排序
for(j=1;j<i;j++){
if(a[j].avg<a[j+1].avg){
exchange(a[j],a[j+1]);
}
}
}
int sum=0,ans=0;
for(i=1;i<=n;i++){
if(sum+w[a[i].index]<=c){ //按序放入物品直到放不下即可
sum+=w[a[i].index];
ans+=v[a[i].index];
x[a[i].index]=1;
}
}
fprintf(fp2,"第%d组数据:\n",num++);
fprintf(fp2,"%d\n",ans);
for(i=1;i<=n;i++){
fprintf(fp2,"%d %d\n",i,x[i]);
}
}
finish = clock();
duration = (double)(finish - start) / CLOCKS_PER_SEC;
fprintf(fp2,"%f seconds\n", duration ); /*此duration单位为秒*/
fclose(fp1);
fclose(fp2);
}
3.回溯法:
#include<stdio.h>
#include "stdlib.h"
#include "time.h"
int c,n,bestv,cv,cw; //bestv表示最优值,cv表示当前背包价值,cw表示当前背包重量
int w[5100],v[5100],x[5100],ax[5100]; //x(i)和ax(i)都表示第i种物品是否放入(1/0),其中x(i)为遍历过程中临时记录每条路径的值,ax(i)记录最优路径的值
struct A{
double avg; //物品单位重量价值
int index; //物品编号,即下标
}a[5100];
void exchange(A &x1,A &x2){ //交换两结构体类型变量的值
double temp1=x1.avg;
x1.avg=x2.avg;
x2.avg=temp1;
int temp2=x1.index;
x1.index=x2.index;
x2.index=temp2;
}
int bound(int i){ //计算价值上界
int left=c-cw; //剩余容量
int b=cv; //当前背包价值
while(i<=n&&left>=w[i]){ //将能整个装入的前几个物品装入背包
b+=v[i];
left-=w[i];
i++;
}
if(i<=n) //若背包容量有剩余,装入下一个物品的部分
b+=(int)(v[i]*left*1.0/w[i]);
return b;
}
void getbestv(int i){ //回溯遍历求最优解
if(i>n){ //此路径结束
if(cv>bestv){ //若当前价值大于最优值
bestv=cv; //更新最优值
for(int j=1;j<=n;j++){ //ax记录最优路径
ax[j]=x[j];
}
}
return;
}
if(cw+w[i]<=c){ //左子树,第i件放得下
x[a[i].index]=1; //记录路径
//放入背包
cv+=v[i];
cw+=w[i];
getbestv(i+1);//搜索下一节点
//回退
cv-=v[i];
cw-=w[i];
}
x[a[i].index]=0; //回退
if(bound(i+1)>=bestv) //右子树,若预计往下走能得到的价值上界不小于当前最优值才执行
getbestv(i+1); //搜索下一节点
}
int main(){
clock_t start, finish; /*精确到ms(毫秒)级的时间*/
double duration; /*测量一个事件持续的时间*/
start = clock();
FILE *fp1,*fp2;
fp1=fopen("bigdata.txt","r");
fp2=fopen("test.txt","w");
int num=1;
while(!feof(fp1)){
fscanf(fp1,"%d%d",&c,&n);
int i,j,ww[5100],vv[5100];
for(i=1;i<=n;i++){
fscanf(fp1,"%d%d",&ww[i],&vv[i]);
a[i].index=i;
a[i].avg=1.0*vv[i]/ww[i];
x[i]=0;
}
cv=0;cw=0;bestv=0;
for(i=n;i>0;i--){ //按单位重量价值降序排序
for(j=1;j<i;j++){
if(a[j].avg<a[j+1].avg){
exchange(a[j],a[j+1]);
}
}
}
for(i=1;i<=n;i++){ //物品重量价值也按单位重量价值降序排序
w[i]=ww[a[i].index];
v[i]=vv[a[i].index];
}
getbestv(1); //回溯遍历获取最优值
fprintf(fp2,"第%d组数据:\n",num++);
fprintf(fp2,"%d\n",bestv);
for(i=1;i<=n;i++){
fprintf(fp2,"%d %d\n",i,ax[i]);
}
}
finish = clock();
duration = (double)(finish - start) / CLOCKS_PER_SEC;
fprintf( fp2,"%f seconds\n", duration ); /*此duration单位为秒*/
printf("%f seconds\n",duration);
fclose(fp1);
fclose(fp2);
}
4.优先队列式分支限界法:
#include<stdio.h>
#include "stdlib.h"
#include "time.h"
int c,n,bestv,cv,cw,up,qlen; //bestv表示最优值,cv和cw分别表示当前背包价值和重量,up表示当前节点往下走预计能得到的价值上界,qlen表示优先队列长度
int w[5100],v[5100],x[5100];
bool vis[5100]; //vis(i)标记队列中第i个元素是否被删除
struct A{
int index; //表示物品下标
double avg; //表示物品单位重量价值
}a[5100];
struct B{
int upvalue; //节点的价值上界
int weight; //到此节点对应的重量
int value; //到此节点对应的价值
int level; //节点所在层次
int islchild; //是否为左节点
int parent; //父节点在队列数组中的下标
}q[5100];
void exchange(A &x1,A &x2){ //交换两个结构体A变量值
double temp1=x1.avg;
x1.avg=x2.avg;
x2.avg=temp1;
int temp2=x1.index;
x1.index=x2.index;
x2.index=temp2;
}
int bound(int i){ //计算右子树价值上界
int left=c-cw; //剩余容量
int b=cv; //当前背包价值
while(i<=n&&left>=w[i]){ //将可以完整放入的前几个物品放入背包
b+=v[i];
left-=w[i];
i++;
}
if(i<=n){ //放入下一个背包的部分
b+=(int)(v[i]*left*1.0/w[i]);
}
return b;
}
int deleteMax(){ //删除队列中价值上界最大的节点并返回
int max=0;
int i;
for(int j=1;j<qlen;j++){ //求价值上界最大的节点
if(vis[j]&&q[j].upvalue>max){
max=q[j].upvalue;
i=j;
}
}
vis[i]=false; //标志为已删除
return i;
}
void getbestv(){ //求最优值
int i=1;
up=bound(1); //从第1个节点开始计算的价值上界
int father=0; //初始化父节点
while(i!=n+1){ //共n层,i=n+1表示已遍历完毕
if(cw+w[i]<=c){ //放的下第i件物品
if(cv+v[i]>bestv) //更新最优值
bestv=cv+v[i];
vis[qlen]=true; //设置即将新添加节点为未访问
//将第i+1个节点添加到优先队列中
q[qlen].upvalue=up;
q[qlen].weight=cw+w[i];
q[qlen].value=cv+v[i];
q[qlen].level=i+1;
q[qlen].islchild=1;
q[qlen++].parent=father;
}
up=bound(i+1);//求出从第i+1个节点开始计算的价值上界,即不取第i个节点
if(up>=bestv){ //若可能得到更优解,添加此节点到队列中
vis[qlen]=true;
q[qlen].upvalue=up;
q[qlen].weight=cw;
q[qlen].value=cv;
q[qlen].level=i+1;
q[qlen].islchild=0;
q[qlen++].parent=father;
}
father=deleteMax(); //得到队列中价值上界最大的节点,即在加入i与不加人i中选择一种较优的方式,作为解树的一个节点
//从此节点往下求值
up=q[father].upvalue;
cw=q[father].weight;
cv=q[father].value;
i=q[father].level;
}
for(i=n;i>0;i--){ //从下往上得到最优解节点
x[a[i].index]=q[father].islchild;
father=q[father].parent;
}
}
int main(){
clock_t start, finish; /*精确到ms(毫秒)级的时间*/
double duration; /*测量一个事件持续的时间*/
start = clock();
FILE *fp1,*fp2;
fp1=fopen("in.txt","r");
fp2=fopen("分支限界法out.txt","w");
int num=1;
while(!feof(fp1)){
fscanf(fp1,"%d%d",&c,&n);
int i,j,ww[5100],vv[5100];
for(i=1;i<=n;i++){
fscanf(fp1,"%d%d",&w[i],&v[i]);
ww[i]=w[i];vv[i]=v[i];
a[i].index=i;
a[i].avg=1.0*v[i]/w[i];
x[i]=0;
}
qlen=1;cv=0;cw=0;bestv=0;
for(i=n;i>1;i--){ //按单位重量价值排序
for(j=1;j<i;j++){
if(a[j].avg<a[j+1].avg){
exchange(a[j],a[j+1]);
}
}
}
for(i=1;i<=n;i++){ //物品也按单位重量价值排序
w[i]=ww[a[i].index];
v[i]=vv[a[i].index];
}
getbestv();
fprintf(fp2,"第%d组数据:\n",num++);
fprintf(fp2,"%d\n",bestv);
for(i=1;i<=n;i++){
fprintf(fp2,"%d %d\n",i,x[i]);
}
}
finish = clock();
duration = (double)(finish - start) / CLOCKS_PER_SEC;
fprintf(fp2,"%f seconds\n", duration ); /*此duration单位为秒*/
fclose(fp1);
fclose(fp2);
}