1、前言
前段時間忙着搞畢業論文,看書效率不高,導致博客一個多月沒有更新了。前段時間真是有些墮落啊,混日子的感覺,很少不爽。今天開始繼續看算法導論。今天繼續學習動態規划和貪心算法。首先簡單的介紹一下動態規划與貪心算法的各自特點及其區別。然后針對0-1背包問題進行討論。最后給出一個簡單的測試例子,聯系動態規划實現0-1背包問題。
2、動態規划與貪心算法
關於動態規划的總結請參考http://www.cnblogs.com/Anker/archive/2013/03/15/2961725.html。這里重點介紹一下貪心算法的過程。貪心算法是通過一系列的選擇來給出某一個問題的最優解,每次選擇一個當前(看起來是)最佳的選擇。貪心算法解決問題的步驟為:
(1)決定問題的最優子結構
(2)設計出一個遞歸解
(3)證明在遞歸的任一階段,最優選擇之一總是貪心選擇。保證貪心選擇總是安全的。
(4)證明通過貪心選擇,所有子問題(除一個意外)都為空。
(5)設計出一個實現貪心策略的遞歸算法。
(6)將遞歸算法轉換成迭代算法。
什么時候才能使用貪心算法的呢?書中給出了貪心算法的兩個性質,只有最優化問題滿足這些性質,就可采用貪心算法解決問題。
(1)貪心選擇性質:一個全局最優解可以通過舉辦最優解(貪心)選擇來達到。即:當考慮做選擇時,只考慮對當前問題最佳的選擇而不考慮子問題的結果。而在動態規划中,每一步都要做出選擇,這些選擇依賴於子問題的解。動態規划一般是自底向上,從小問題到大問題。貪心算法通常是自上而下,一個一個地做貪心選擇,不斷地將給定的問題實例規約為更小的子問題。
(2)最優子結構:問題的一個最優解包含了其子問題的最優解。
動態規划與貪心的區別:
貪心算法:
(1)貪心算法中,作出的每步貪心決策都無法改變,因為貪心策略是由上一步的最優解推導下一步的最優解,而上一部之前的最優解則不作保留;
(2)由(1)中的介紹,可以知道貪心法正確的條件是:每一步的最優解一定包含上一步的最優解。
動態規划算法:
(1)全局最優解中一定包含某個局部最優解,但不一定包含前一個局部最優解,因此需要記錄之前的所有最優解 ;
(2)動態規划的關鍵是狀態轉移方程,即如何由以求出的局部最優解來推導全局最優解 ;
(3)邊界條件:即最簡單的,可以直接得出的局部最優解。
3、0-1背包問題描述
有一個竊賊在偷竊一家商店時發現有n件物品,第i件物品價值為vi元,重量為wi,假設vi和wi都為整數。他希望帶走的東西越值錢越好,但他的背包中之多只能裝下W磅的東西,W為一整數。他應該帶走哪幾樣東西?
0-1背包問題中:每件物品或被帶走,或被留下,(需要做出0-1選擇)。小偷不能只帶走某個物品的一部分或帶走兩次以上同一個物品。
部分背包問題:小偷可以只帶走某個物品的一部分,不必做出0-1選擇。
4、0-1背包問題解決方法
0-1背包問題是個典型舉辦子結構的問題,但是只能采用動態規划來解決,而不能采用貪心算法。因為在0-1背包問題中,在選擇是否要把一個物品加到背包中,必須把該物品加進去的子問題的解與不取該物品的子問題的解進行比較。這種方式形成的問題導致了許多重疊子問題,滿足動態規划的特征。動態規划解決0-1背包問題步驟如下:
0-1背包問題子結構:選擇一個給定物品i,則需要比較選擇i的形成的子問題的最優解與不選擇i的子問題的最優解。分成兩個子問題,進行選擇比較,選擇最優的。
0-1背包問題遞歸過程:設有n個物品,背包的重量為w,C[i][w]為最優解。即:
課后習題給出了偽代碼:
5、編程實現
現在給定3個物品,背包的容量為50磅。物品1重10磅,價值為60,物品2重20磅,價值為100,物品3重30磅,價值為120。采用動態規划可以知道最優解為220,選擇物品2和3。采用C++語言實現如下:
1 #include <iostream> 2 using namespace std; 3 4 //物品數據結構 5 typedef struct commodity 6 { 7 int value; //價值 8 int weight; //重量 9 }commodity; 10 11 const int N = 3; //物品個數 12 const int W = 50; //背包的容量 13 14 //初始物品信息 15 commodity goods[N+1]={{0,0},{60,10},{100,20},{120,30}}; 16 int select[N+1][W+1]; 17 18 int max_value(); 19 20 int main() 21 { 22 int maxvalue = max_value(); 23 cout<<"The max value is: "; 24 cout<<maxvalue<<endl; 25 int remainspace = W; 26 //輸出所選擇的物品列表: 27 for(int i=N; i>=1; i--) 28 { 29 if (remainspace >= goods[i].weight) 30 { 31 if ((select[i][remainspace]-select[i-1][remainspace-goods[i].weight]==goods[i].value)) 32 { 33 cout << "item " << i << " is selected!" << endl; 34 remainspace = remainspace - goods[i].weight;//如果第i個物品被選擇,那么背包剩余容量將減去第i個物品的重量 ; 35 } 36 } 37 } 38 return 0; 39 } 40 int max_value() 41 { 42 //初始沒有物品時候,背包的價值為0 43 for(int w=1;w<=W;++w) 44 select[0][w] = 0; 45 for(int i=1;i<=N;++i) 46 { 47 select[i][0] = 0; //背包容量為0時,最大價值為0 48 for(int w=1;w<=W;++w) 49 { 50 if(goods[i].weight <= w) //當前物品i的重量小於等於w,進行選擇 51 { 52 if( (goods[i].value + select[i-1][w-goods[i].weight]) > select[i-1][w]) 53 select[i][w] = goods[i].value + select[i-1][w-goods[i].weight]; 54 else 55 select[i][w] = select[i-1][w]; 56 } 57 else //當前物品i的重量大於w,不選擇 58 select[i][w] = select[i-1][w]; 59 } 60 } 61 return select[N][W]; //最終求得最大值 62 }
程序測試結果如下: