01揹包問題的解決

廣大菜鳥發表於2020-11-10

測試案例:http://poj.org/problem?id=3624

需求:0-1揹包問題(下面程式碼在poj上不是超時就是記憶體消耗過大,畢竟演算法的時間複雜度是O(nW))
xj =01的揹包問題
輸入格式:
行數	內容
1:		N M	(種類,最大重量)
2~1+N:	W_i D_i	(單一重量,價值)
狀態轉移方程:f(i,j) = max(f(i,j),f(i-1,j-ui)+wi)
例如:
		輸入:
		4 6
		1 4
		2 6
		3 12
		2 7
		輸出:
		23

1、二維陣列動態規劃

1.1、Runtime error的版本

狀態轉移方程:f(i,j) = max(f(i,j),f(i-1,j-ui)+wi)

#include<iostream>
#include<math.h>
#define MAX_N 3402		// 最大種類數
#define MAX_W 400		//最大單一種類重量
#define MAX_D 100		// 最大價值
#define MAX_M 12880		//最大限重
using namespace std;

 //dp[k][y] 表示在前k中物品中選擇,揹包重量不超過y時揹包最大價值
int dp[MAX_N+1][MAX_M+1];
int weights[MAX_N+1] ;
int values[MAX_N+1] ;

int main() {
	int n,m,i,j;
	cin >> n >> m;
	for (i = 1; i <= n; i++) {
		cin >> weights[i] >> values[i];
	}
	// (1)初始化
	for (i = 0; i <= m; i++) {
		dp[0][i] = 0;	/*不買東西,裝入重量再多的非有價值的情況*/
	}
	for (i = 0; i <= n; i++) {
		dp[i][0] = 0;	/*多種東西無價值*/
	}

	// (2)動態規劃
	for (i = 1; i <= n; i++) {	//對前i個物品
		for (j = 1; j <= m; j++) {//對前j個重量
			// 在 同個重量級的前i-1個種類組合 和 在(j-weights[i])的情況下利用前k種物品轉入獲取最大值再加當前物品價值 中選擇
			if (j < weights[i])		// 這時候揹包容量不足以放下第 i 件物品,只能選擇不拿,繼續考慮前i-1件物品
				dp[i][j] = dp[i - 1][j];
			else					//這時揹包容量可以放下第 i 件物品,我們可以考慮拿或者不拿,分析怎樣價值更大:
				dp[i][j] = max(dp[i - 1][j], dp[i-1][j- weights[i]] + values[i]);
		} 
	}
	cout << dp[n][m] << endl;
}

雖然這個演算法在公式上沒有錯誤,但是使用的多行是多餘的,只需要一行儲存就可以,不過,這個版本是《演算法設計與分析》課本教的,可能是為了好理解吧

1.2 展示路徑


#include<iostream>
#include<math.h>
#define MAX_N 3402		// 最大種類數
#define MAX_W 400		//最大單一種類重量
#define MAX_D 100		// 最大價值
#define MAX_M 12880		//最大限重
using namespace std;

 //dp[k][y] 表示在前k中物品中選擇,揹包重量不超過y時揹包最大價值
 //		不裝第k個物品或者至少裝一個第k種物品
int dp[MAX_N+1][MAX_M+1];
int I[MAX_N + 1][MAX_M + 1]; //標誌陣列,方便回溯,計算優化函式值dp[k][y]時用到物品的最大編號
int weights[MAX_N+1] ;
int values[MAX_N+1] ;

void TrackSolution(int n,int m) {
	int x[MAX_N+1] = { 0 };	//n中商品裝入量
	int y = m, j = n;	// I[J][y]
	while (I[j][y] != 0) {
		j = I[j][y];
		while (I[j][y] == j) {
			y = y - weights[j];
			x[j]++;
		}
	}
	for (int i = 1; i <= n; i++) {
		cout << x[i] << " ";
	}
	cout << endl;
}

int main() {
	int n,m,i,j;
	cin >> n >> m;
	for (i = 1; i <= n; i++) {
		cin >> weights[i] >> values[i];
	}
	// (1)初始化
	for (i = 0; i <= m; i++) {
		dp[0][i] = 0;	/*不買東西,裝入重量再多的非有價值的情況*/
	}
	for (i = 0; i <= n; i++) {
		dp[i][0] = 0;	/*多種東西無價值*/
	}

	// (2)動態規劃
	for (i = 1; i <= n; i++) {	//對前i個物品
		for (j = 1; j <= m; j++) {//對前j個重量
			// 在 同個重量級的前i-1個種類組合 和 在(j-weights[i])的情況下利用前k種物品轉入獲取最大值再加當前物品價值 中選擇
			if (j < weights[i])
				dp[i][j] = dp[i - 1][j];
			else 
				dp[i][j] = max(dp[i - 1][j], dp[i-1][j- weights[i]] + values[i]);

			if (j < weights[i] || (dp[i - 1][j] > dp[i][j - weights[i]] + values[i]))
				I[i][j] = I[i - 1][j];
			else
				I[i][j] = i;
		} 
	}
	cout << dp[n][m] << endl;
	TrackSolution(n, m);	
}

在這裡插入圖片描述

2、回溯法

2.1 、Time Limit Exceeded

#include<stdio.h>
#define MAX_M 12880		//最大限重
#define MAX_N 3402		// 最大種類數
int c;//揹包容量
int n;//物品種類數量
int weight[MAX_N+1];	//存放n個物品種類的陣列
int price[MAX_N+1];		//存放價值的陣列
int currentWeight = 0;	//當前重量
int currentPrice = 0;	//當前價值
int bestPrice = 0;		//當前最優值
int bp = 0;				//全域性最優價值

void Backtracking(int i) {
	if (i > n) {
		if (bestPrice > bp) {
			bp = bestPrice;
		}
		return;
	}
	if (currentWeight + weight[i] <= c) {
		// 把物品i放入揹包,搜尋左子樹
		currentWeight += weight[i];
		bestPrice += price[i];
		Backtracking(i + 1);	
		currentWeight -= weight[i];
		bestPrice -= price[i];
	}
	Backtracking(i + 1);
}


int main() {
	int i;
	scanf_s("%d%d", &n,&c);
	for (i = 1; i <= n; i++) {
		scanf_s("%d%d", &weight[i], &price[i]);
	}
	Backtracking(1);
	printf("%d\n", bp);
}

2.2 展示路徑

#include<stdio.h>
#define MAX_M 12880		//最大限重
#define MAX_N 3402		// 最大種類數
int c;//揹包容量
int n;//物品種類數量
int weight[MAX_N+1];	//存放n個物品種類的陣列
int price[MAX_N+1];		//存放價值的陣列
int currentWeight = 0;	//當前重量
int currentPrice = 0;	//當前價值
int bestPrice = 0;		//當前最優值
int bestAnswer[MAX_M];	//當前最優解
int bp = 0;				//全域性最優價值
int bA[MAX_M+1];			//全域性最優路徑

void Backtracking(int i) {
	if (i > n) {
		if (bestPrice > bp) {
			bp = bestPrice;
			for (int j = 1; j <= n; j++) {
				bA[j] = bestAnswer[j];
			}
		}
		return;
	}
	if (currentWeight + weight[i] <= c) {
		// 把物品i放入揹包,搜尋左子樹
		bestAnswer[i] = 1;
		currentWeight += weight[i];
		bestPrice += price[i];
		Backtracking(i + 1);	
		currentWeight -= weight[i];
		bestPrice -= price[i];
	}
	bestAnswer[i] = 0;
	Backtracking(i + 1);
}


int main() {
	int i;
	scanf("%d%d", &n,&c);
	for (i = 1; i <= n; i++) {
		scanf("%d%d", &weight[i], &price[i]);
	}
	Backtracking(1);
	printf("%d\n", bp);
	for (i = 1; i <= n; i++)
		printf("%d ", bA[i]);
}

在這裡插入圖片描述

3、一維陣列動態規劃 AC

注意,for (int j = m; j >= weights[i]; j--)是逆序遍歷總重量

// 一維陣列解法
#include<iostream>
#define MAX_M 12880		//最大限重
#define MAX_N 3402		// 最大種類數
using namespace std;

int dp[MAX_M + 1];
int weights[MAX_N + 1];
int values[MAX_N + 1];

int main() {
	int n, m, i, j;
	cin >> n >> m;
	for (i = 1; i <= n; i++) {
		cin >> weights[i] >> values[i];
	}
	for (int i = 1; i <= n; i++) {	// 種類
		for (int j = m; j >= weights[i]; j--) {	//容量
			dp[j] = max(dp[j], dp[j - weights[i]] + values[i]);
		}
	}
	cout << dp[m] << endl;
}

在這裡插入圖片描述

相關文章