0-1揹包和完全揹包

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];
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章