01揹包的四種解法詳解:動態規劃,貪心法,回溯法,優先佇列式分支限界法(C語言編寫)
阿新 • • 發佈:2019-01-04
最近剛完成了演算法課程設計,題目是用多種解法解決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.動態規劃法:
2.貪心法:#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); }
3.回溯法:#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); }
#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);
}