0-1揹包問題
核心:問題分解、狀態轉移
1問題及求解
一堆物品,編號1,2,…,n,重量w1,w2,…,wn,價值v1,v2,…,vn,現在有一個包,能承受的最大重量為w,問選裝哪幾件物品,能帶走的價值最大?
核心:問題分解、狀態轉移
分解:對每個物品就兩種情況,要麼裝,要麼不裝,那麼我們從最後一個物品n開始逐個考察。
狀態轉移:對第i個物品,當前揹包容量為j,當前揹包內最大價值為val[i][j],
如果它的重量比當前揹包容量大,放棄,看下一個,即 val[i][j]=val[i-1][j];
如果它的重量不大於當前揹包容量,那裝還是不裝,按總價值大的來,
i)不裝的話,最大總價值和前面一樣,揹包容量不變,即a = val[i-1][j];
ii)裝的話,最大總價值為前面最大總價值加上當前價值,揹包容量減去該物品質量,即 b = val[i-1][j-w[i]]+v[i];
則,val[i][j]=max(a,b).
2 示例
編號1-6的物品資訊為:重量陣列w = {4, 6, 2, 2, 5, 1},價值陣列v = {8, 10, 6, 3, 7, 2}。求揹包容量C = 12時,選哪幾件物品總價值最大?最大價值是多少?
求C=12的過程依賴於前面各個過程結果,列出價值矩陣如下(自下而上、自左向右求得),所以當c=12時的最大價值為24。
那具體放入了那幾件物品呢?看最後一列,從最後一個逐個和上一個值做比較,如果相等,說明該物品沒有放入揹包,如果不等,則放入了揹包。容易看出揹包中的物品為第3,2,1號。
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
1 |
0 |
0 |
0 |
8 |
8 |
8 |
8 |
8 |
8 |
8 |
8 |
8 |
2 |
0 |
0 |
0 |
8 |
8 |
10 |
10 |
10 |
10 |
18 |
18 |
18 |
3 |
0 |
6 |
6 |
8 |
8 |
14 |
14 |
16 |
16 |
18 |
18 |
24 |
4 |
0 |
6 |
6 |
9 |
9 |
14 |
14 |
17 |
17 |
19 |
19 |
24 |
5 |
0 |
6 |
6 |
9 |
9 |
14 |
14 |
17 |
17 |
19 |
21 |
24 |
6 |
2 |
6 |
8 |
9 |
11 |
14 |
16 |
17 |
19 |
19 |
21 |
24 |
3 C++實現
迴圈或遞迴均可,註釋已標註
a.迴圈實現
#include<iostream>
using namespace std;
int main(){
int N=6;
int C=12;
int v[N+1]={0,8,10,6,3,7,2};
int w[N+1]={0,4,6,2,2,5,1};
int val[N+1][C+1];//價值矩陣
//針對第0個物品,無論揹包大小是多少,所能取得的最大價值均為0
for(int j=0;j<C+1;j++) val[0][j]=0;
for(int i=1;i<=N;i++){
for(int j=1;j<=C;j++){
//如果當前揹包容量j小於當前物品i的重量,則不裝i物品
if(j<w[i]) val[i][j]=val[i-1][j];
//如果當前揹包容量大於等於當前物品i的重量,則選擇裝它或不裝它中較大的值
else val[i][j]=max(val[i-1][j-w[i]]+v[i],val[i-1][j]);
}
}
cout<<val[N][C]<<endl;
//從高到低,逐個判斷是否裝在了揹包中
//判斷方法:逐個與前一個比較,若價值相等,說明該物品未裝入包中
int b[N+1];
for(int i=N;i>=1;i--){
if(val[i][C]==val[i-1][C]) b[i]=0;
else{
b[i]=1;
cout<<i<<' ';
C=C-w[i];
}
}
return 0;
}
b.遞迴實現
#include<iostream>
using namespace std;
//遞迴實現,返回第1至第i個物品可裝入的最大價值,當前揹包容量c,w[]為重量,v[]為對應的價值
int val(int i,int c,int w[],int v[]){
//第0個物品價值為0
if(i==0) return 0;
//如果當前揹包容量小於當前物品重量,則不放入,最大價值同val(i-1,c,w,v)
else if(c<w[i-1]) return val(i-1,c,w,v);
//如果當前揹包容量大於等於當前物品重量,放入或不放入,看哪個價值大
else{
int a=val(i-1,c-w[i-1],w,v)+v[i-1];//放入後總價值
int b=val(i-1,c,w,v);//不放入總價值
return max(a,b);
}
}
int main(){
int v[6]={8,10,6,3,7,2};
int w[6]={4,6,2,2,5,1};
int m=6;//第m個物品
int c=12;//揹包容量
cout<<val(m,c,w,v)<<endl;//最大價值
//從高到低,逐個判斷是否裝在了揹包中
//判斷方法:逐個與前一個比較,若價值不相等,說明該物品放入了包中
for(int i=6;i>=1;i--){
if(val(i,c,w,v)!=val(i-1,c,w,v)){
cout<<i<<' ';
}
}
return 0;
}