01背包的四种解法详解:动态规划,贪心法,回溯法,优先队列式分支限界法(C语言编写)

来源:互联网 发布:mysql 字段加一 编辑:程序博客网 时间:2024/06/06 16:32

最近刚完成了算法课程设计,题目是用多种解法解决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);}