算法思路
動態規劃
- 動態規劃算法與分治法類似,其基本思想是將待求解問題分解成若干個子問題,先求解子問題,然後從這些子問題的解得到原問題的解。
- 可以用一個表來記錄所有已解的子問題的答案。不管該子問題以後是否被用到,只要它被計算過,就將其結果填入表中。這就是動態規劃法的基本思路。
- 具體的動態規劃算法多種多樣,但它們具有相同的填表格式。
問題描述
物品數n=5,揹包容量c=10。
物品重量序列w={2,2,6,5,4},物品價值序列v={6,3,5,4,6}。
求最佳裝包序列。
解決思路
//假設簡單題目:揹包容量5,物品ABC
A | B | C | |
---|---|---|---|
重量 | 2 | 3 | 4 |
價值 | 2 | 2 | 3 |
(可以直接推出:選AB價值總和最高,可最後驗證是否正確)
1. 構建動態規劃表
//表橫軸爲揹包當前容量12345、縱軸爲物品序號ABC。
//表格內容意義爲當前容量(橫軸)的最優價值
1. 將第一行第一列設爲0.
0 | 1 | 2 | 3 | 4 | 5 | |
---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 |
A | 0 | |||||
B | 0 | |||||
C | 0 |
2. 從(1,A)處開始填數,
-
A過程:如果該物品質量(ABC)大於當前揹包容量(123456…)
- 則不放入:現行揹包總價值爲之前的價值,即上格內容。
-
B過程:如果該物品質量(ABC)小於等於揹包容量(123456…)
-
則判斷價值AB大小,如果A>B則裝入,否則不裝入。
-
M:(裝自己的重量)的最優價值
-
N:(不裝自己的重量)的最優價值
即M : W =當前揹包容量 - 當前物品重量(自重)的容量 M = 表格上行當前容量W 對應的價值 + 自己的價值。 即N = 不放自己的價值,即上面表格的價值。
-
-
該步驟完成可得下表:題目參考: ABC重量分別爲234、價值分別爲223
0 | 1 | 2 | 3 | 4 | 5 | |
---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 |
A | 0 | 0 | 2 | 2 | 2 | 2 |
B | 0 | 0 | 2 | 2 | 2 | 5 |
C | 0 | 0 | 2 | 3 | 3 | 5 |
//舉例:例如(5,B)格的5是怎麼來的:
- 到A過程
- 物品B質量爲3,當前揹包容量爲5,3<5,不符合A。
- 到B過程:
- B價值爲上格:2。
- A價值爲當前揹包容量5 - 當前物品重量(自重)3 = 2 ,容量2所對應的價值,即在(A,2)的價值2 再加上自己的價值3 = 5
因爲 2 < 5 所以取5填入當前空
//舉例:例如(5,C)格的5是怎麼來的:
- 到A過程
- C質量爲4,當前揹包容量爲5,不符合A。
- 到B過程:
- N價值爲上格:5。
- M價值爲當前揹包容量5-當前物品重量(自重)4 = 1 容量所對應的價值
即在(A,1)的價值0 再加上自己的價值4 = 4
因爲 4 < 5所以取5填入當前空
2. 回溯找到相同的格子
//起點設置在右下角
-
a.如果該格 與 上面一格價值相同
- 那麼上移 直到不相同 然後到b步
-
b.如果該格 與 上面一格價值不同
- 則將當前物品記錄到 選中的集合 中
- 然後找到左邊 (當前容量減去當前物品重量) 的揹包容量處
- 接着回到第a步,一直到邊界
//過程如下(深綠色爲要取的物品,要記錄的值。淡綠色爲移動過程)
3. 得到要裝入的物品:AB
總結
先構造動態規劃表,然後回溯獲得最優解。
代碼解決
C語言代碼
#include<stdio.h>
#define MAX_SIZE 5//物品數量
#define BAG_SIZE 10//揹包容量
int main() {
int w[MAX_SIZE] = {2,2,6,5,4 };//重量
int v[MAX_SIZE ] = {6,3,5,4,6 };//價值
int table[MAX_SIZE + 1][BAG_SIZE + 1];//table表定義 每個元素都是當前容量下揹包的最優價值
int yes[MAX_SIZE]; //選中的物品集
int yes_size = 0; //選中集大小
//建立 表
for (int i = 0; i <= MAX_SIZE; i++)//i是商品序號 從1開始
for (int j = 0; j <= BAG_SIZE; j++) {//j是當前揹包容量 從0開始
if (i == 0 || j == 0) {
table[i][j] = 0;//使第一行以及第一列全爲0
continue;
}
if (w[i-1] > j)
table[i][j] = table[i - 1][j];//如果質量大於揹包容量,則不放入。 現行揹包總價值爲之前的價值
else
table[i][j] = table[i - 1][j] > (table[i - 1][j - w[i-1]] + v[i-1]) ?
table[i - 1][j] : (table[i - 1][j - w[i - 1]] + v[i - 1]);
//如果質量小於等於揹包容量,則判斷價值大小
//(裝自己的重量)的最優價值A 與 (不裝自己的重量)的最優價值B
//即A = (當前揹包容量j減去當前物品重量(自重)的容量W)所對應價值,即上面表格那個容量W對應的價值
//再加上自己的價值。
//即B = 不放自己的價值,即上面表格的價值。
}
//接下來回溯table表獲取選中的物品
int i = MAX_SIZE;//縱軸物品最下側
int j = BAG_SIZE;//橫軸容量最右側
while (i > 0 && j > 0) {//如果定位點不在邊界
while (table[i][j] == table[i - 1][j])//如果該格 與 上面一格價值相同
i--;//上移 一直到不相同
yes[yes_size++] = --i;//將該物品(因爲物品數組所以下標要-1)記錄到選中的集合中
j = j - w[i-1];//橫向跳動,跳到減去剛纔記錄的物品的重量後的容量的橫軸位置
}
printf("被選中的物品有:\n");
for (int i = 0; i < yes_size; i++)
printf("重量爲%d,價值爲%d的物品\n", w[yes[i]], v[yes[i]]);//循環輸出剛纔選中的物品
return 0;//end main()
}