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

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


    原文作者:分支限界法
    原文地址: https://blog.csdn.net/u010729348/article/details/17219995
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞