標籤:動態規劃 01背包 c++
動態規劃
我在上一篇部落格裡已經講了一點動態規划了,傳送門:演算法學習 - 動態規劃(DP問題)(C++)
這裡說一下,遇到動態規劃應該如何去想,才能找到解決辦法。
最主要的其實是要找狀態轉移的方程,例如上一篇部落格裡面,找的就是當前兩條生產線的第i個station的最短時間和上一時刻的時間關係。
minTime(station[1][i]) = minTime(station[1][i-1] + time[i], station[2][i-1] + time[i] + trans[i-1])
今天要講的問題是01背包問題。
01背包問題描述
01背包是在M件物品取出若干件放在空間為W的背包裡,每件物品的體積為W1,W2……Wn,與之相對應的價值為P1,P2……Pn。
求解這個背包能裝的最大價值。(物體不能分割)。
求解思路
這個其實比上一道題難在哪裡了呢?
難在上一題裝配線始終是兩個,不會多,不會少。我們變化的量只有裝配站的多少。
這個題目變化的量一個是物品的數量,還有一個是背包的空間。就是說裝配線不會隨著station的多少而變化,但是背包的空間會隨著物品的裝入而變化。
所以我們需要做的是判斷當背包剩餘的空間為j
的時候,能否裝入第i
個物品,並且總價值增加。
即:
d[j] = max ( d[j], d[j-v[i]] + w[i]); //v[i]表示第i個物品的體積,w[i]表示第i個物品的價值。
其實我最開始看到上面這個狀態轉移的方程的時候,覺得這不是肯定的在空間j
的時候放入物品,價值大於不放入物品的。畢竟只要放入就證明增加了價值啊。
其實不是這個樣子的,因為只有在最開始都沒有裝入的時候,所有的都是0. 裝入就一定價值增加。但是假如我們的物品如下:
物品1: 空間 2 價值 5
物品2: 空間 4 價值 3
當我們放入第一件物品的時候,假設背包空間是5,那麼d[2…5]都是為 5. 因為d[0…1]空間不夠所以為0。 當我們放入物品2的時候,d[5] = 5 > d[5 - 4] + 3
因為d[1] = 0;
。
發現了沒有! 並不是放入就一定總價值增加!
所以我們遍曆所有物品,從第一個物品開始,找當空間為j
的時候,裝入物品是否會增加價值!
代碼實現
代碼其實行數很少,不好看那些寫了兩個螢幕的,可能並沒有更多的功能。
//// main.cpp// DP_01backpack//// Created by Alps on 15/4/28.// Copyright (c) 2015年 chen. All rights reserved.//// 代碼中直接定義了石頭的數量和背包的空間,其實可以不用提前定義。// 這裡為了方便,而且在C++ 11中可以動態規定數組大小。#include <iostream>using namespace std;#ifndef STONE_NUM#define STONE_NUM 5 //定義石頭數量#endif#ifndef BACKPACK_SPACE#define BACKPACK_SPACE 10#endifint main(int argc, const char * argv[]) { int stoneSpace, stoneValue;//儲存當前輸入的物品空間和價值 int value[BACKPACK_SPACE+1] = {0}; //初始化value數組 for (int i = 0; i < STONE_NUM; i++) { scanf("%d %d", &stoneSpace, &stoneValue);//接收物品輸入 for (int j = BACKPACK_SPACE; j > 0; j--) {//當背包空間在不同的時候,** j 一定是從大到小** if (j >= stoneSpace && value[j] < (value[j-stoneSpace] + stoneValue)) {//假如背包空間足夠,並且放入總價值增加 value[j] = value[j-stoneSpace] + stoneValue;//放入,並更新總價值 } } } printf("%d\n", value[BACKPACK_SPACE]); return 0;}
這裡解釋下,為何第二層for
迴圈裡,j 是從最大變化到最小的。因為我們比較的是當前物品放入,價值是否有變化,就是 value[j] 和 value[j - v] 比較。 假如更新了,那麼 value[j + 1] 需要和 value[j + 1 - v] 比較的值,可能 value[j + 1 - v]已經被更新過了。就不行了!因為那個時候value[j + 1 -v]的總價值已經算上當前的物品了。再算就重複了。
放入哪些物品
上面的代碼沒有知道到底放入了哪些物品,其實是因為為了節省空間的,沒有儲存每個 stoneSpace和 stoneValue.
所以我們把它們變成數組就好了。
然後我們首先要做的是:找到一共背包用了多少空間!
這個很重要,因為背包的最大價值不一定是裝滿了,所以我們要找到用了多少空間,才能知道到底放入了哪些物品。
怎麼找呢?
假如我們的value數組如下:
0
0
4
6
9
10
13
15
15
19
19
很容易知道,這個背包剩餘了一個空間? 為什麼呢,因為最大的19有兩個,j 多了1但是價值沒有增加,說明這 1的空間沒有放入物品,也就是空餘了。
這樣找到第一個最大得數的下標,就是使用的空間了。
代碼
比較簡單,只不過多了一步尋找那些物品放入了,就是當背包空間減去物品空間,總價值也剛好增加了物品的價值數量,那麼說明這個物品被放入了。
代碼如下:
//// main.cpp// DP_01backpack//// Created by Alps on 15/4/28.// Copyright (c) 2015年 chen. All rights reserved.//// 代碼中直接定義了石頭的數量和背包的空間,其實可以不用提前定義。// 這裡為了方便,而且在C++ 11中可以動態規定數組大小。#include <iostream>using namespace std;#ifndef STONE_NUM#define STONE_NUM 5 //定義石頭數量#endif#ifndef BACKPACK_SPACE#define BACKPACK_SPACE 10#endifint main(int argc, const char * argv[]) { int value[BACKPACK_SPACE+1] = {0}; int stoneSpace[STONE_NUM],stoneValue[STONE_NUM]; for (int i = 0; i < STONE_NUM; i++) { scanf("%d %d", &stoneSpace[i], &stoneValue[i]); for (int j = BACKPACK_SPACE; j > 0; j--) { if (j >= stoneSpace[i] && value[j] < value[j-stoneSpace[i]] + stoneValue[i]) { value[j] = value[j-stoneSpace[i]] + stoneValue[i]; } } }//以上代碼幾乎無變化,只是儲存是數組儲存了。 for (int i = 0; i <= BACKPACK_SPACE; i++) { printf("%d\n", value[i]); }//列印value數組,這裡面存放的是,所有物品在背包只有空間i的時候,達到的最大總價值。 int backPackSpace = BACKPACK_SPACE;// 得到背包空間 while (value[backPackSpace] == value[backPackSpace-1]) { backPackSpace--;//假如背包價值和背包空間-1的時候價值相同,空間 -1 }//找到一共使用了多少空間 for (int i = STONE_NUM-1; i >= 0; i--) { if (value[backPackSpace] == value[backPackSpace-stoneSpace[i]] + stoneValue[i]) { //假如減去當前物品的空間,總價值剛好和物品的價值相等,說明此物品被放入了。 printf("%d ", i+1);//列印這個物品。i+1是因為物品的下標是從0開始的。 backPackSpace = backPackSpace - stoneSpace[i]; //背包放入了這個物品,自然空間減少了。 } } return 0;}
整個代碼比較簡單。 有疑問的留言,可以相互交流。
我看了一篇其他部落格,用二維矩陣入門,也覺得很棒。推薦:http://www.hawstein.com/posts/dp-knapsack.html
演算法學習 - 01背包問題(動態規劃C++)