問題描述:有n件物品和一個容量為c的背包。第i件物品的價值是v[i],重量是w[i]。求解將哪些物品裝入背包可使價值總和最大。所謂01背包,表示每一個物品只有一個,要么裝入,要么不裝入。
今 天下午的算法復習課,老師提的各種算法經典問題時,出現頻率就是01背包問題了!動態規划、回溯法、分支限界法,在貪心算法時也提到注意背包問題,當然 01背包問題不能用貪心算法實現,不能保證能得到最優解。回溯法是最近學的,所以試着用C語言將其實現了下,下面作以分析,后期將會繼續用其他兩種算法實 現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()
{
inti,j;
inttemporder = 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)
{
doublebound(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)
{
doubleleftw= 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;
returnb;
}
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]);
}
return0;
}
運行結果截圖:
算法復雜度分析:上界函數bound()需要O(n)時間,在最壞的情況下有O(2^n)個右子結點需要計算上界,回溯算法backtrack需要的計算時間為O(n2^n)。
go to sleep......