問題
給定n種物品和一個背包,物品(1<=i<=n)重量是wI ,其價值vi, 背包容量為C,對每種物品只有兩種選擇:裝入背包和不裝入背包,即物品是不可能部分裝入,部分不裝入。如何選擇裝入背包的物品,使其價值最大?
想法
該問題是最優化問題,求解此問題一般采用動態規划(dynamic plan),很容易證明該問題滿足最優性原理。
動態規划的求解過程分三部分:
一:划分子問題:將原問題划分為若干個子問題,每個子問題對應一個決策階段,並且子問題之間具有重疊關系
二:確定動態規划函數:根據子問題之間的重疊關系找到子問題滿足遞推關系式(即動態規划函數),這是動態規划的關鍵
三:填寫表格:設計表格,以自底向上的方式計算各個子問題的解並填表,實現動態規划過程。
思路:
如何定義子問題?0/1背包可以看做是決策一個序列(x1,x2,x3,…,xn),對任何一個變量xi的決策時xi=1還是xi=0. 設V(n,C)是將n個物品裝入容量為C的背包時背包所獲得的的最大價值,顯然初始子問題是將前i個物品裝如容量為0的背包中和把0個物品裝入容量為j的背包中,這些情況背包價值為0即
V(i,0)=V(0,j)=0 0<=i<=n, 0<=j<=C
接下來考慮原問題的一部分,設V(I,j)表示將前i個物品裝入容量為j的背包獲得的最大價值,在決策xi時,已經確定了(x1,x2,…,xi-1),則問題處於下列兩種情況之一:
- 背包容量不足以裝入物品i,則裝入前i-1個物品的最大價值和裝入前i個物品最大價值相同,即xi=0,背包價值沒有增加
-
背包容量足以裝入物品i, 如果把物品i裝入背包,則背包物品價值等於把前i-1個物品裝入容量為j-wi的背包中的價值加上第i個物品的價值vi;如果第i個物品沒有裝入背包,則背包價值等於把前i-1個物品裝入容量為j的背包中所取得的價值,顯然,取二者最大價值作為把物品i裝入容量為j的背包中的最優解,得到如下遞推公式
為了確定裝入背包中的具體物品,從V(n,C)的值向前推,如果V(n,C)>V(n-1,C),
則表明第n個物品被裝入背包中,前n-1個物品被裝入容量為C-wn的背包中;否則,第n個物品沒有被裝入背包中,前n-1個物品被裝入容量為C的背包中,依次類推,直到確認第一個物品是否被裝入背包中
代碼C++實現
-
// dp_01Knapsack.cpp : 定義控制台應用程序的入口點。
-
#include<iostream>
-
#include<algorithm>
-
using namespace std;
-
const int N = 5;
-
const int Capacity = 20;
-
int flag[N+1] = { 0 };// 物品是否在背包中,下標從 1開始算
-
int V[N+ 1][Capacity + 1] = { 0 }; // 造表記錄子問題的最優解
-
int Knapsack(int w[], int v[], int n, int C);// 實際物品數目,背包容量
-
int main()
-
{
-
int w[] = {0,3,2,1,4,5};
-
int v[] = { 0,25,20,15,40,50 };
-
int n = 5, C = 6;
-
int maxValue = Knapsack(w, v, n, C);
-
cout << maxValue;
-
return 0;
-
}
-
int Knapsack(int w[], int v[], int n, int C) {
-
for (int i = 0; i <= n; ++i)
-
V[i][0] = 0;
-
for (int j = 0; j <= C; ++j)
-
V[0][j] = 0;
-
for(int i=1;i<=n;++i)
-
for (int j = 1; j <= C; ++j)
-
{
-
if (j < w[i])
-
V[i][j] = V[i - 1][j];
-
else {
-
V[i][j] = max(V[i - 1][j], V[i - 1][j - w[i]] + v[i]);
-
}
-
}
-
-
for (int i = n, j = C; i > 0; --i)
-
{
-
if (V[i][j] > V[i - 1][j])
-
{
-
flag[i] = 1;
-
j -= w[i];
-
}
-
else
-
flag[i] = 0;
-
}
-
cout << " 造表\n ";
-
for (int i = 0; i <= n; ++ i)
-
{
-
for (int j = 0; j <= C; ++j)
-
cout << V[i][j] << '\t';
-
cout << endl;
-
}
-
-
for (int i = 1; i <= n; ++i)
-
cout << flag[i] << '\t';
-
cout << endl;
-
return V[n][C];
-
}
結果如下:
表格分析如下
|
|
0 |
1 |
2 |
3 |
4 |
5 |
6 |
flag |
|
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
|
W1=3,v1=25 |
1 |
0 |
0 |
0 |
25 |
25 |
25 |
25 |
0 |
W2=2,v2=20 |
2 |
0 |
0 |
20 |
25 |
25 |
45 |
45 |
0 |
W3=1,v3=15 |
3 |
0 |
15 |
20 |
35 |
40 |
45 |
45 |
1 |
W4=4,v4=40 |
4 |
0 |
15 |
20 |
35 |
40 |
55 |
60 |
0 |
W5=5,v5=50 |
5 |
0 |
15 |
20 |
35 |
40 |
55 |
65 |
1 |
算法性能分析:
在算法Knapsack中,第一個for循環的時間性能是O(n),第二個for循環的時間性能是O(C),第三個循環是兩層嵌套for循環,時間性能是O(n*C),第四個for循環時間性能是O(n);
因此算法時間復雜度O(n*C)