問題描述:
給定n種物品和一背包。物品i的重量是wi,其價值為vi,背包的容量為C。問:應如何選擇裝入背包的物品,使得裝入背包中物品的總價值最大?例如
形式化描述:給定c >0, wi >0, vi >0 , 1≤i≤n.要求找一n元向量(x1,x2,…,xn,), xi∈{0,1}, ∋ ∑ wi xi≤c,且∑ vi xi達最大.即一個特殊的整數規划問題。
問題解析:0-1背包問題是子集選取問題。0-1 背包問題的解空間可以用子集樹表示。在搜索解空間樹時,只要其左兒子節點是一個可行節點,搜索就進入左子樹。當右子樹中有可能含有最優解時,才進入右子樹搜索。否則,將右子樹剪去。設r是當前剩余物品價值總和,cp是當前價值;bestp是當前最優價值。當cp+r<=bestp時,可剪去右子樹。計算右子樹上界的更好的方法是將剩余物品依次按其單位價值排序,然后依次裝入物品,直至裝不下時,再裝入物品一部分而裝滿背包。
例如:對於0-1背包問題的一個實例,n=4,c=7,p=[9,10,7,4],w=[3,5,2,1]。這4個物品的單位重量價值分別為[3,2,3,5,4]。以物品單位重量價值的遞減序裝入物品。先裝入物品4,然后裝入物品3和1.裝入這3個物品后,剩余的背包容量為1,只能裝0.2的物品2。由此得一個解為[1,0.2,1,1],其相應價值為22。盡管這不是一個可行解,但可以證明其價值是最優值的上界。因此,對於這個實例,最優值不超過22。
在實現時,由Bound計算當前節點處的上界。類Knap的數據成員記錄解空間樹中的節點信息,以減少參數傳遞調用所需要的棧空間。在解空間樹的當前擴展節點處,僅要進入右子樹時才計算上界Bound,以判斷是否可將右子樹剪去。進入左子樹時不需要計算上界,因為上界預期父節點的上界相同。算法的具體實現如下:
代碼實現:

#include <iostream> #include <stdio.h> //#include <conio.h> using namespace std; int n;//物品數量 double c;//背包容量 double v[100];//各個物品的價值 value double w[100];//各個物品的重量 weight double cw = 0.0;//當前背包重量 current weight double cp = 0.0;//當前背包中物品總價值 current value double bestp = 0.0;//當前最優價值best price double perp[100];//單位物品價值(排序后) per price int order[100];//物品編號 int put[100];//設置是否裝入,為1的時候表示選擇該組數據裝入,為0的表示不選擇該組數據 //按單位價值排序 void knapsack() { int i,j; int temporder = 0; double temp = 0.0; for(i=1;i<=n;i++) perp[i]=v[i]/w[i]; //計算單位價值(單位重量的物品價值) for(i=1;i<=n-1;i++) { for(j=i+1;j<=n;j++) if(perp[i]<perp[j])//冒泡排序perp[],order[],sortv[],sortw[] { temp = perp[i]; //冒泡對perp[]排序 perp[i]=perp[i]; perp[j]=temp; temporder=order[i];//冒泡對order[]排序 order[i]=order[j]; order[j]=temporder; temp = v[i];//冒泡對v[]排序 v[i]=v[j]; v[j]=temp; temp=w[i];//冒泡對w[]排序 w[i]=w[j]; w[j]=temp; } } } //回溯函數 void backtrack(int i) { //i用來指示到達的層數(第幾步,從0開始),同時也指示當前選擇玩了幾個物品 double bound(int i); if(i>n) //遞歸結束的判定條件 { bestp = cp; return; } //如若左子節點可行,則直接搜索左子樹; //對於右子樹,先計算上界函數,以判斷是否將其減去 if(cw+w[i]<=c)//將物品i放入背包,搜索左子樹 { cw+=w[i];//同步更新當前背包的重量 cp+=v[i];//同步更新當前背包的總價值 put[i]=1; backtrack(i+1);//深度搜索進入下一層 cw-=w[i];//回溯復原 cp-=v[i];//回溯復原 } if(bound(i+1)>bestp)//如若符合條件則搜索右子樹 backtrack(i+1); } //計算上界函數,功能為剪枝 double bound(int i) { //判斷當前背包的總價值cp+剩余容量可容納的最大價值<=當前最優價值 double leftw= c-cw;//剩余背包容量 double b = cp;//記錄當前背包的總價值cp,最后求上界 //以物品單位重量價值遞減次序裝入物品 while(i<=n && w[i]<=leftw) { leftw-=w[i]; b+=v[i]; i++; } //裝滿背包 if(i<=n) b+=v[i]/w[i]*leftw; return b;//返回計算出的上界 } int main() { int i; printf("請輸入物品的數量和背包的容量:"); scanf("%d %lf",&n,&c); /*printf("請輸入物品的重量和價值:\n"); for(i=1;i<=n;i++) { printf("第%d個物品的重量:",i); scanf("%lf",&w[i]); printf("第%d個物品的價值是:",i); scanf("%lf",&v[i]); order[i]=i; }*/ printf("請依次輸入%d個物品的重量:\n",n); for(i=1;i<=n;i++){ scanf("%lf",&w[i]); order[i]=i; } printf("請依次輸入%d個物品的價值:\n",n); for(i=1;i<=n;i++){ scanf("%lf",&v[i]); } knapsack(); backtrack(1); printf("最優價值為:%lf\n",bestp); printf("需要裝入的物品編號是:"); for(i=1;i<=n;i++) { if(put[i]==1) printf("%d ",order[i]); } return 0; }
算法效率:
計算上界需要O(n)時間,在最壞情況下有O(2^n)個右兒子節點需要計算上界,故解0-1背包問題的回溯算法所需要的計算時間為O(n2^n)。
運行結果:
參考文獻:王曉東《算法設計與分析》
https://blog.csdn.net/qian2213762498/article/details/79420269?depth_1-