問題描述:
有n件物品和一個容量為c的背包。第i件物品的價值是v[i],重量是w[i]。求解將哪些物品裝入背包可使價值總和最大。所謂01背包,表示每一個物品只有一個,要么裝入,要么不裝入。
回溯法:
01背包屬於找最優解問題,用回溯法需要構造解的子集樹。在搜索狀態空間樹時,只要左子節點是可一個可行結點,搜索就進入其左子樹。對於右子樹時,先計算上界函數,以判斷是否將其減去,剪枝啦啦!
上界函數bound():當前價值cw+剩余容量可容納的最大價值<=當前最優價值bestp。
為了更好地計算和運用上界函數剪枝,選擇先將物品按照其單位重量價值從大到小排序,此后就按照順序考慮各個物品。
#include <stdio.h> #include <conio.h> int n;//物品數量 double c;//背包容量 double v[100];//各個物品的價值 double w[100];//各個物品的重量 double cw = 0.0;//當前背包重量 double cp = 0.0;//當前背包中物品價值 double bestp = 0.0;//當前最優價值 double perp[100];//單位物品價值排序后 int order[100];//物品編號 int put[100];//設置是否裝入 //按單位價值排序 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[i]=perp[i]; perp[j]=temp; temporder=order[i]; order[i]=order[j]; order[j]=temporder; temp = v[i]; v[i]=v[j]; v[j]=temp; temp=w[i]; w[i]=w[j]; w[j]=temp; } } } //回溯函數 void backtrack(int i) { double bound(int i); if(i>n) { bestp = cp; return; } if(cw+w[i]<=c) { 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) { double leftw= c-cw; double b = 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("請輸入物品的重量和價值:"); for(i=1;i<=n;i++) { printf("第%d個物品的重量:",i); scanf("%lf",&w[i]); printf("價值是:"); scanf("%lf",&v[i]); order[i]=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; }
時間復雜度分析:
上界函數bound()需要O(n)時間,在最壞的情況下有O(2^n)個右子結點需要計算上界,回溯算法backtrack需要的計算時間為O(n2^n)