0-1揹包與完全揹包的不同
本文轉自CSDN,原文:https://blog.csdn.net/qiaoruozhuo/article/details/76167137
分析:
0-1揹包和完全揹包問題的區別在於前者同一種物品最多選一次,而後者同一種物品可多次選取。我們使用B[i][j]表示從前i件物品中選出若干件物品放在容量爲j的揹包中,所得的最大價值,可以得到二者的狀態方程分別爲:
0-1揹包問題:B[i][j] = B[i-1][j],其中j < W[i];
或者B[i][j] = max(B[i-1][j], B[i-1][j-W[i]] + P[i]),其中j >= W[i]。
完全揹包問題:B[i][j] = B[i-1][j],其中j < W[i];
或者B[i][j] = max(B[i-1][j], B[i][j-W[i]] + P[i]),其中j >= W[i]。
二者狀態方程的區別在於:
0-1揹包問題中,若取了1件第i個物品,則總容量變爲j-W[i],剩下的只能在前i-1件物品中去取了,其最大總價值爲B[i-1][j-W[i]] + P[i];
完全揹包問題中,若取了1件第i個物品,則總容量變爲j-W[i],剩下的仍可以在前i件物品中去取,其最大總價值爲B[i][j-W[i]] + P[i];
一維數組優化算法:
0-1揹包問題:
B[i][j] = max(B[i-1][j], B[i-1][j-W[i]] + P[i]),即第i行第j列的元素,由第i-1行的元素決定,且列座標j大的元素由j小的元素決定,若我們用一維數組F[j]代替B[i][j],則只記錄了列座標,未記錄行座標,在同一行中,必須先求出列座標較大的元素,再求列座標小的元素,這樣先改變的是下標j較大的元素,且其不會影響j小的元素。故在內層循環中,應該讓循環變量j的值從大到小遞減。
完全揹包問題:
B[i][j] = max(B[i-1][j], B[i][j-W[i]] + P[i]),即第i行第j列的元素,可能是等於第i-1行第j列的元素,也可能由第i行第j-W[i]列的元素決定,故必須先求出同一行中列座標j較小的元素,用來計算j較大的元素。 若我們用一維數組F[j]代替B[i][j],則只記錄了列座標,未記錄行座標,在同一行中,必須先求出列座標j較小的元素,再求j大的元素,故在內層循環中,應該讓循環變量j的值從小到大遞增(與0-1揹包問題剛好相反)。
總結:
二者的狀態方程很相似,區別在於B[i][j]是由上一行的較小列座標決定,還是由同一行的較小列座標決定,使用二維數組記錄最優解時,我們可以直接根據狀態方程求B[i][j],因爲同時記錄了元素的行座標和列座標值,故無論內層循環的循環變量j是遞增還是遞減,都不影響計算結果。但是,若使用一維數組F[j]代替B[i][j],則只記錄了列座標,未記錄行座標,需要考慮先改變列座標j較大的元素還是j較小的元素。在0-1揹包問題中,須先求出列座標j較大的元素,故讓循環變量j的值從大到小遞減;而完全揹包問題中,須先求出列座標j較小的元素,故讓循環變量j的值從小到大遞增。
代碼
#include<iostream>
#include<cmath>
using namespace std;
const int MAXC = 12880; //揹包最大容量
const int MAXN = 3402; //物品的個數
int W[MAXN+1];//物品的重量
int P[MAXN+1];//物品的價值
int F1[MAXC+1]; //記錄裝入容量爲c的揹包的最大價值
int B1[MAXN+1][MAXC+1]; //備忘錄,記錄給定n個物品裝入容量爲c的揹包的最大價值
int F2[MAXC+1]; //記錄裝入容量爲c的揹包的最大價值
int B2[MAXN+1][MAXC+1]; //備忘錄,記錄給定n個物品裝入容量爲c的揹包的最大價值
int Best_1(int n, int c); //0-1揹包問題:二維數組記錄最優解
int Best_2(int n, int c);//0-1揹包問題:一維數組記錄最優解
int Best_3(int n, int c);//完全揹包問題:二維數組記錄最優解
int Best_4(int n, int c);//完全揹包問題:一維數組記錄最優解
int main()
{
int n, c;
cin >> n >> c;
for (int i=1; i<=n; i++)//不計下標爲0的元素
{
cin >> W[i] >> P[i];
}
cout << Best_1(n, c) << endl;
cout << Best_2(n, c) << endl;
cout << Best_3(n, c) << endl;
cout << Best_4(n, c) << endl;
return 0;
}
int Best_1(int n, int c)//0-1揹包問題:二維數組記錄最優解
{
//記錄前i(1<=i<n)個物品裝入容量爲0-c的揹包的最大價值
for (int i=1; i<n; i++)
{
for (int j=0; j<W[i]; j++)//揹包容量不夠,不能裝下第i件物品
{
B1[i][j] = B1[i-1][j];
}
for (int j=W[i]; j<=c; j++)//揹包容量足夠,可以選擇裝或不裝第i件物品
{
if (B1[i-1][j] < B1[i-1][j-W[i]] + P[i])
B1[i][j] = B1[i-1][j-W[i]] + P[i];
}
}
//因爲第n個物品最多裝一次,故只要容量夠,未裝滿與裝滿的價值是一樣的,即B1[n][c]==B1[n][j],其中W[n]<=j<=c
//所以對第n個物品來說,只需考慮容量恰好爲c的情況,這樣可以減少計算量
if (c < W[n]) //如果容量不夠
{
B1[n][c] = B1[n-1][c]; //先默認爲不裝第n個物品
}
else
{
B1[n][c] = max(B1[n-1][c], B1[n-1][c-W[n]]+P[n]);
}
return B1[n][c];
}
int Best_2(int n, int c)//0-1揹包問題:一維數組記錄最優解
{
//爲簡化代碼,沒有把i==n的情形單獨拿出來處理,若需要單獨處理第n個物品,可參考Best_1()
for (int i=1; i<=n; i++)
{//須先求出列座標j較大的元素,故讓循環變量j的值從大到小遞減
for (int j=c; j>=W[i]; j--)
{//當(j < W[i] || F1[j] > F1[j-W[i]] + P[i])時,F1[j]的值不變
if (F1[j] < F1[j-W[i]] + P[i])
F1[j] = F1[j-W[i]] + P[i];
}
}
return F1[c];
}
int Best_3(int n, int c)//完全揹包問題:二維數組記錄最優解
{
for (int i=1; i<=n; i++)
{
for (int j=1; j<W[i]; j++)//容量不夠,則和給定i-1個物品裝入容量爲j的揹包的結果一致
{
B2[i][j] = B2[i-1][j];
}
for (int j=W[i]; j<=c; j++)
{//B2[i][j-W[i]]表示給定i個物品裝入容量爲j-W[i]的揹包,質量爲W[i]的物品可能裝了多個
B2[i][j] = max(B2[i-1][j], B2[i][j-W[i]] + P[i]);
}
}
return B2[n][c];
}
int Best_4(int n, int c)//完全揹包問題:一維數組記錄最優解
{
for (int i=1; i<=n; i++)
{//須先求出列座標j較小的元素,故讓循環變量j的值從小到大遞增
for (int j=W[i]; j<=c; j++)
{//當(j < W[i] || F2[j] > F2[j-W[i]] + P[i])時,F2[j]的值不變
if (F2[j] < F2[j-W[i]] + P[i])
F2[j] = F2[j-W[i]] + P[i];
}
}
return F2[c];
}