0-1揹包問題-動態規劃 解釋與C語言實現

算法思路

動態規劃
  • 動態規劃算法與分治法類似,其基本思想是將待求解問題分解成若干個子問題,先求解子問題,然後從這些子問題的解得到原問題的解。
  • 可以用一個表來記錄所有已解的子問題的答案。不管該子問題以後是否被用到,只要它被計算過,就將其結果填入表中。這就是動態規劃法的基本思路。
  • 具體的動態規劃算法多種多樣,但它們具有相同的填表格式。

問題描述

物品數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是怎麼來的:

  1. A過程
    • 物品B質量爲3,當前揹包容量爲5,3<5,不符合A。
  2. B過程
    • B價值爲上格:2。
    • A價值爲當前揹包容量5 - 當前物品重量(自重)3 = 2 ,容量2所對應的價值,即在(A,2)的價值2 再加上自己的價值3 = 5
      因爲 2 < 5 所以取5填入當前空

//舉例:例如(5,C)格的5是怎麼來的:

  1. A過程
    • C質量爲4,當前揹包容量爲5,不符合A。
  2. B過程
    • N價值爲上格:5。
    • M價值爲當前揹包容量5-當前物品重量(自重)4 = 1 容量所對應的價值
      即在(A,1)的價值0 再加上自己的價值4 = 4
      因爲 4 < 5所以取5填入當前空

2. 回溯找到相同的格子

//起點設置在右下角

  • a.如果該格 與 上面一格價值相同

    • 那麼上移 直到不相同 然後到b步
  • b.如果該格 與 上面一格價值不同

    1. 則將當前物品記錄到 選中的集合
    2. 然後找到左邊 (當前容量減去當前物品重量) 的揹包容量處
    3. 接着回到第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()
}

運行結果

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章