1. 程式人生 > >0-1揹包:使用滾動陣列時為何要逆序列舉

0-1揹包:使用滾動陣列時為何要逆序列舉

問題簡述:有一揹包,最大體積是10,有三個物品,體積分別是3,4,5,重量分別是4,5,6,求在不超過揹包體積的前提下,所放物品的最大重量是多少。

答:最大重量是11,選擇的物品是2和3,其體積是9,小於揹包體積10

我們已經知道,對於0-1揹包問題,我們可以使用動態規劃進行解決。

定義f(i,j):把前i個物品裝入體積為j的揹包中的最大重量

那麼:f(i,j)=max{f(i-1,j),f(i-1,j-v[i]+w[i]}

根據該方程,我們可以用下面的程式解決上述問題:

#include <iostream>
#include <string.h>
#include <algorithm>
#include <stdio.h> 
using namespace std;

int main()
{
	int max_v = 10;				// 揹包最大體積 
	int v[4] = {0,3,4,5};		// 體積 
	int w[4] = {0,4,5,6};		// 重量 
	int f[11][11];
	
	memset(f,0,sizeof(f));
	
	// f(i,j):把前i個物品裝到容量是j的揹包中的最大總重量 
	// f(i,j)=max{f(i-1,j),f(i-1,j-v[i]+w[i]}
	// 一定要牢記:我們要計算的是:f(3,10)  !!!   什麼意思?對我們來講,11*11的陣列,只有f(3,10)對我們來講是有意義的,其它的空間能省則省。^_^ 
	for (int i=1; i<=3; i++)
	{	
		for (int j=0;j<=10; j++)
		{
			f[i][j] = f[i-1][j];		// 如果少了這句,當j<v[i]時,f[i][j]將不被更新,那麼f[2][3]可就是0而不是4了。 
			if (j >= v[i])
			{
				f[i][j]= max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
			}
		}
	}
	
	// 輸出
	for (int i=0; i<=3; i++)
	{
		for (int j=0; j<=10; j++)
		{
			printf("%4d",f[i][j]); 
		}
		printf("\n"); 
	}
	
	printf("The only answer we need is %d\n",f[3][10]);
}
輸出結果:


對這個答案我們滿意嗎?

如果從最終答案的角度來講,可以接受,畢竟我們得出了想要的解。可是,當資料量很大的時候,比如有m個物品,揹包體積是n,那麼我們需要定義陣列為f[m][n],其空間複雜度為O(m*n)。而誠如我們所說過的那樣,我們僅僅想知道的是揹包能裝下的最大重量(不要求列印方案),即f[n][m]的值,那麼,有必要申請n*m的陣列嗎?或者說,有無方法可以降低空間複雜度呢?答案是有的,即滾動陣列。

觀察上圖,除最後一行外,其餘資料是多餘的.....

由f(i,j)=max{f(i-1,j),f(i-1,j-v[i]+w[i]},我們可以簡化為f(j)=max{f(j),f(j-v[i])+w[i]}

程式如下:

#include <iostream>
#include <string.h>
#include <algorithm>
#include <stdio.h> 
using namespace std;

int main()
{
	int max_v = 10;				// 揹包最大體積 
	int v[4] = {0,3,4,5};		// 體積 
	int w[4] = {0,4,5,6};		// 重量 
	int f[11];
	
	memset(f,0,sizeof(f));
	
	// f(j):容量是j的揹包中的最大總重量 
	// f(j)=max{f(j),f(j-v[i]+w[i]}
	for (int i=1; i<=3; i++)
	{	
		for (int j=10;j>=0; j--)  // 此處要逆序列舉容量
		{
			if (j >= v[i])
			{
				f[j]= max(f[j],f[j-v[i]]+w[i]);
			}
		}
		// 輸出
		for (int j=0; j<=10; j++)
		{
			printf("%4d",f[j]); 
		}
		printf("\n"); 
	}
	
	printf("The only answer we need is %d\n",f[10]);
}

輸出結果:


為什麼計算時要逆序列舉?計算順序所決定!請看下圖:


如當i=1時,f(10)=max{f(10),f(10-v[1])+w[1]}=max{f(10),f(7)+w[1]}=max{0,4}=4

即f(10)依賴的是f(10)和f(7)的值,需要注意的是,此時的f(10)和f(7)是i=0時的f(10)和f(7),如果使用二維陣列儲存,無需擔心覆蓋問題,對體積的列舉逆序或正序都可,但是如果使用一維陣列,若正序列舉會先計算f(7),那麼,再計算f(10)時,i=0時的f(7)已被覆蓋矣!