題目:
有 n 種物品和乙個容量為 v 的揹包,每種物品都有無限件可用。第 i 種物品的費用是 c[i],價值是 w[i] 。求解將哪些物品裝入揹包可使這些物品的費用總和不超過揹包容量,且價值總和最大。
這個問題可以不可以像01揹包問題一樣使用動態規劃來求解呢?來證明一下即可。
首先,先用反證法證明最優化原理:
假設完全揹包的解為f(n1,n2,...,nn)(n1,n2 分別代表第1、第2件物品的選取數量),完全揹包的子問題為,將前i種物品放入容量為t的揹包並取得最大價值,其對應的解為:f(n1,n2,...,ni),假設該解不是子問題的最優解,即存在另一組解f(m1,m2,...,mi),使得f(m1,m2,...,mi) > f(n1,n2,...,ni),那麼f(m1,m2,...,mi,...,nn) 必然大於 f(n1,n2,...,nn),因此 f(n1,n2,...,nn) 不是原問題的最優解,與原假設不符,所以f(n1,n2,...,ni)必然是子問題的最優解。
再來看看無後效性:
對於子問題的任意解,都不會影響後續子問題的解,也就是說,前i種物品如何選擇,只要最終的剩餘揹包空間不變,就不會影響後面物品的選擇。即滿足無後效性。
因此,完全揹包問題也可以使用動態規劃來解決。
這個問題非常類似於 01揹包問題,所不同的是每種物品都有無限件。也就是從每種物品的角度考慮,與它相關的策略已並非取或不取兩種,而是有取 0 件、取 1 件、取 2 件 ......等很多種。如果仍然按照解 01揹包時的思路,令 f[i][v] 表示前 i 種物品恰放入乙個容量為 v 的揹包的最大權值。仍然可以按照每種物品不同的策略寫出狀態轉移方程:狀態轉移方程:
這跟 01揹包問題一樣有 o(vn) 個狀態需要求解,但求解每個狀態的時間已經不是常數了,求解狀態 f[i][v] 的時間是
基本**如下:
#include #include #define n 1002
using namespace std;
int f[n][n];
int w[n];
int v[n];
int main()
for ( int i = 1; i <= n; i++ ) }}
cout << f[n][w] 狀態轉移方程:同 01 揹包問題一樣,完全揹包問題空間複雜度可以優化到θ(n)1。
空間複雜度優化後**如下:
#include #include #define n 1002
using namespace std;
int f[n];
int w[n];
int v[n];
int main()
for ( int i = 1; i <= n; i++ ) }}
cout << f[w] <
完全揹包問題有乙個很簡單有效的優化,是這樣的:若兩件物品 i 、j 滿足
) 實現,一般都可以承受。
然而這個並不能改善最壞情況的複雜度,因為有可能特別設計的資料可以一件物品也去不掉。另外,針對揹包問題而言,比較不錯的一種方法是:首先將費用大於 v 的物品去掉,然後使用類似計數排序的做法,計算出費用相同的物品中價值最高的哪個,可以 θ(
v +
n) 地完成這個優化。
既然 01揹包問題是最基本的揹包問題,那麼我們可以考慮把完全揹包問題轉化為 01揹包問題來解。最簡單的想法是,考慮到第 i 種物品最多選 v/c[i] 件,於是可以把第 i 種物品轉化為 v/c[i] 件費用及價值均不變的物品,然後求解這個 01揹包問題。
這樣完全沒有改進基本思路的複雜度,但畢竟給了我們將完全揹包問題轉化為 01揹包問題的思路:將一種物品拆成多件物品。
更高效的轉化方法是:把第 i 種物品拆成費用為
這個演算法使用一維陣列,先看偽**:
for i=1..n
for v=0..v
f[v]=max
你會發現,這個偽**與 01揹包問題的偽**只有v的迴圈次序不同而已。值得一提的是,上面的偽**中兩層for迴圈的次序可以顛倒。這個結論有可能會帶來演算法時間常數上的優化。為什麼這樣一改就可行呢?首先想想為什麼01揹包問題中要按照v=v..0的逆序來迴圈。
這是因為要保證第i次迴圈中的狀態f[i][v]是由狀態f[i-1][v-c[i]]遞推而來。換句話說,這正是為了保證每件物品只選一次,保證在考慮「選入第i件物品」這件策略時,依據的是乙個絕無已經選入第i件物品的子結果f[i-1][v-c[i]]。
而現在完全揹包的特點恰是每種物品可選無限件,所以在考慮「加選一件第i種物品」這種策略時,卻正需要乙個可能已選入第i種物品的子結果f[i][v-c[i]],所以就可以並且必須採用v=0..v的順序迴圈。這就是這個簡單的程式為何成立的道理。
這個演算法也可以以另外的思路得出。例如,將基本思路中求解f[i][v-c[i]]的狀態轉移方程顯式地寫出來,代入原方程中,會發現該方程可以等價地變形成這種形式:
f[i][v]=max
這個方程表示的意思:**:「 將前 i 件物品放入容量為 v 的揹包中 」 這個子問題,若只考慮第 i 件物品的策略(放多少個),那麼就可以轉化為乙個只牽扯前 i 件物品的問題。
如果不放第 i 件物品,那麼問題就轉化為 「 前 i-1 件物品放入容器為 v 的揹包中 」 ,價值為 f[i-1][v] ;
如果放第 i 件物品,那麼問題就轉化為 「 前 i 件物品(這裡不是前 i-1 的原因是,這裡的物品都有無限件,可能此前已經放了第 i 件物品)放入剩下的容量為 v-c[i] 的揹包中 」,揹包中此時能獲得的最大價值就是 f[i][v-c[i]] 價值再加上通過放入第 i 件物品獲得的價值 w[i]。
#include #include #define n 1002
using namespace std;
int f[n];
int w[n];
int v[n];
int main()
for ( int i = 1; i <= n; i++ )
}cout << f[w] <
return 0;
}
procedure completepack(cost,weight)
for v=cost..v
f[v]=max
動態規劃揹包問題 完全揹包
問題描述 有n種物品,每種均有無窮多個。第i個物品的體積為vi,重量為wi。選一些物品裝到容量為c的揹包中,使得揹包內物品在總體積不超過c的前提下重量盡量大。問題分析 開乙個陣列f i j 表示前i種物品中選取若干件物品放入剩餘空間為j的揹包中所能得到的最大重量。每種物品無窮個,所以還要有乙個k遍歷...
動態規劃 揹包問題 完全揹包
有n種物品和乙個容量為v的揹包,每種物品都有無限件可用。第i種物品的費用是w i 價值是v i 求解將哪些物品裝入揹包可使這些物品的費用總和不超過揹包容量,且價值總和最大。貪心 y or n 因為每件物品都可以選取任意件,你也許會想到貪心演算法 選取價值最高的就好了 看上去沒什麼毛病,但是有乙個問題...
動態規劃揹包問題 完全揹包
問題背景描述 你有乙個容量為v的揹包,現在有n種物品供你選擇,每件物品可以選擇無數次,每種物品所佔的空間為c i 價值為v i 現在讓你作出最佳方案,使揹包中的總價值最大。有了之前01揹包的基礎,我們很快就能寫出完全揹包的狀態轉移方程 f i j max 但是這樣的時間複雜度就很大了o v v c ...