簡單揹包問題
首先不好意思,前段時間在做項目,現在項目結束了,有時間了,來爲大家更新下算法!
看題:
小紅和小明在魔法石礦裏挖到了很多的魔法石,他們有一個揹包,可以放入的重量爲S,現有N件魔法石,重量分別爲W1,W2,W3,…Wn,各重量均爲正整數,從N件魔法石中挑選若干件,使得放入揹包的重量之和正好爲S。若成功,則輸出放入揹包的物品,否則輸出“Failed!”。
【輸入格式】
第一行兩個整數即S和N,其中S<1000,N<32.第二行爲N個整數,即N件物品的重量。
【輸出格式】
若成功,則輸出放入揹包的物品,以空格間隔,否則輸出“Failed!”.
【輸入樣例】
10 5
12 3 4 5 6
【輸出樣例】
4 6
枚舉算法
★
該問題可以轉化爲某個物品取或不取的問題,以1代表取,以0代表不取,窮舉出所有的可能性,輸出總質量==S的組合即可,下面是輸出所有符合條件的組合:
//簡單揹包問題——枚舉算法
#include<iostream>
using namespace std;
int N,S;
int W[40]; //初始化每個物品的重量
int flag[40]={0}; //標記數組
void Print() //打印結果
{
for(int i=0;i!=N;i++)
if(1 == flag[i])
cout<<W[i]<<" ";
cout<<endl;
}
int main()
{
int sum,all_count=1;
cin>>S>>N;
for(int i=0;i<N;i++)
cin>>W[i];
for(int i=0;i!=N;i++) //計算所有可能性次數,即2的n次方
all_count *= 2;
for(int num=0;num<=all_count;++num)
{
for(int i=0;i!=N;i++) //列舉所有flag數組可能
if(flag[i] == 0)
{
flag[i]=1;
continue;
}
else
{
flag[i]=0;
break;
}
sum = 0; //本次重量初始化爲0
for(int i=0;i!=N;i++) //按標記計算所有選中物品重量和
if(flag[i] == 1)
sum += W[i];
if(sum == S) //打印方案
Print();
}
return 0;
}
下面我們來引入另一種算法
遞歸算法
★
解決此題最可能的方法是一個一個將物品放入揹包內實驗,設布爾函數knapsack(s,n)表示剩下n個物品中裝滿剩下重量爲s的揹包,如果有解,返回1,否則返回0.實驗過程應該如下所述: (1)取最後一個物品Wn,調用knapsack(s,n); (2)如Wn=s,結束程序,輸出結果(n,Wn); (3)如Wn<s,且n>1,則求knapsack(s-Wn,n-1); (4)如Wn>s,且n>1,刪除Wn,從剩下n-1中繼續找,即knapsack(s,n-1)。 還可以得知遞歸結束的條件應爲: (1)Wn=s(正好放入的物品重量等於揹包能裝的重量) (2)Wn!=s(無解) (3)n<=0(再沒有物品可試) 但實際上問題並不是這麼簡單,因爲所選取並放入的物品Wn很可能導致無法獲得正確結果。例如s=10, 物品重量分別爲1,6,2,7,5,如果第一次選擇Wn=5放入揹包後,則後面再怎麼選擇也不可能成功,正確的做法是排除 Wn=5,從Wn=7開始纔可能有正確答案,即7+2+1=10。 因此Wn是否有效還要看後續的knapsack(s-Wn,n-1)是否有解,如果無解,說明先前取得Wn不合適,就要放棄Wn,在剩餘物品中 重新開始挑選,即knapsack(s,n-1)。 參考代碼如下:
//簡單揹包問題——遞歸算法
#include<iostream>
using namespace std;
#define MAXN 40
int W[MAXN]; //各物品重量
int knapsack(int s,int n) //s爲剩餘重量,n爲剩餘可選物品數
{
if(s == 0) //如果正好裝滿
return 1;
if(s<0 || (s>0 && n<1)) //如s<0 或 n<1 則不能完成
return 0;
if(knapsack(s-W[n],n-1)) //從後往前裝,裝上W[n]後,若剩餘物品仍有解
{
cout<<W[n]<<" "; //則裝進第n個包,並輸出
return 1;
}
return knapsack(s,n-1); //如裝了第n個包後,導致無解,則刪除該包,嘗試第n-1個
}
int main()
{
int S,N;
cin>>S>>N;
for(int i=1;i<=N;++i)
cin>>W[i];
if(knapsack(S,N))
cout<<"\n";
else
cout<<"Failed!\n";
}
大家是不是對揹包問題有一定了解了,下面我們引入另一種揹包0/1揹包問題
0/1揹包問題
小紅和小明有一個最多能裝m千克的揹包,有n塊魔法石,它們的重量分別是W1,W2,...Wn,它們的價值分別是爲C1,C2,...Cm。若每種魔法石只有一件,問能裝入的最大總價值。 【輸入示例】 第一行爲兩個整數m和n,以下n行中,每行兩個整數Wi,Ci,分別代表第i件物品的重量和價值 【輸出格式】 輸出一個整數,即最大價值 【輸入樣例】 8 3 2 3 5 4 5 5 【輸出樣例】 8 動態規劃算法
★
在這道題裏,物品或者被裝入揹包,或者不被裝入揹包,只有兩種選擇。因此被稱爲0/1揹包問題。我們可以使用窮舉組合、貪心算法,但是,用窮舉組合可能會超時,貪心法不穩定,往往的不出最優解。由此需要考慮動態規劃算法(DP)。
使用動態規劃算法解決如下:
設f(i,x)表示前i件物品,揹包容量爲x時的最優價值;
則f(i,x)=max{f(i-1,x-W[i])+C[i],f(i-1,x)}
f(n.m)即爲最優解,邊界條件爲f(0,x)=,f(i,0)=0;
如果對上述公式不理解,我們使用題目中的樣例用表格法來分析:
根據邊界條件f(0,x)=0,f(i,0)=0;表格如下所示:
試放入第一個物品,即W=2,C=3試一下在各個重量段所能取得的最大價值如表所示:
如下表所示,試放入第二個物品,即w=5,c=4,試一下在各個重量段所能取得的最大價值:
由於第二個物品重量爲5,所以在1~4重量段時,值仍爲3,當重量段>=5時,可以嘗試放入改物品,即比較改重量段減去5時的價值
加該物品的價值,與不放第二個物品時的總價值哪個大,取其最大值。以後讀入的物品一次類推。
例如:f(2,5)=max(f(1,(5-5))+4,f(1,5))
=max(f(1,0)+4,f(1,5))
=4
F(2,7)=max(f(1,(7-5))+4,f(1,7))
=max(f(1,2)+4,f(1,7))
=7
以此法試放入第三個物品,即w=5,c=5時,表格如圖所示:
最終答案爲8
完整代碼
// 0/1揹包問題
#include<iostream>
#include<cstdlib>
using namespace std;
int i,j,m,n;
int w[200],c[200],f[200][200];
int main()
{
cin>>m>>n;
for(i=1;i<=n;i++)
cin>>w[i]>>c[i];
for(i=1;i<=n;i++)
for(j=1;j<=m;j++)
if(j >= w[i])
f[i][j]=max(f[i-1][j-w[i]]+c[i],f[i-1][j]);
else
f[i][j]=f[i-1][j];
cout<<f[n][m]<<endl;
return 0;
}
到這裏就結束了,希望此篇文章,能夠對大家有所幫助,也希望的算法精益求精,不斷突破瓶頸;下次爲大家講解N皇后問題!