1. 程式人生 > >01背包動態規劃

01背包動態規劃

足夠 訪問 遍歷 減少 出現 相關 比較 16px 結束

有n 個物品,它們有各自的重量和價值,現有給定容量的背包,如何讓背包裏裝入的物品具有最大的價值總和?

技術分享圖片

動態規劃與分治法類似,都是把大問題拆分成小問題,通過尋找大問題與小問題的遞推關系,解決一個個小問題,最終達到解決原問題的效果。但不同的是,分治法在子問題和子子問題等上被重復計算了很多次,而動態規劃則具有記憶性,通過填寫表把所有已經解決的子問題答案紀錄下來,在新問題裏需要用到的子問題可以直接提取,避免了重復計算,從而節約了時間,所以在問題滿足最優性原理之後,用動態規劃解決問題的核心就在於填表,表填寫完畢,最優解也就找到。

過程

  a) 把背包問題抽象化(X1,X2,…,Xn,其中 Xi 取0或1,表示第 i 個物品選或不選),Vi

表示第 i 個物品的價值,Wi表示第 i 個物品的體積(重量);

  b) 建立模型,即求max(V1X1+V2X2+…+VnXn);

  c) 約束條件,W1X1+W2X2+…+WnXn<capacity;

  d) 定義V(i,j):當前背包容量 j,前 i 個物品最佳組合對應的價值;

  e) 最優性原理是動態規劃的基礎,最優性原理是指“多階段決策過程的最優決策序列具有這樣的性質:不論初始狀態和初始決策如何,對於前面決策所造成的某一狀態而言,其後各階段的決策序列必須構成最優策略”。判斷該問題是否滿足最優性原理,采用反證法證明:

    假設(X1,X2,…,Xn)是01背包問題的最優解,則有(X2

,X3,…,Xn)是其子問題的最優解,

    假設(Y2,Y3,…,Yn)是上述問題的子問題最優解,則理應有(V2Y2+V3Y3+…+VnYn)+V1X1 > (V2X2+V3X3+…+VnXn)+V1X1;

    而(V2X2+V3X3+…+VnXn)+V1X1=(V1X1+V2X2+…+VnXn),則有(V2Y2+V3Y3+…+VnYn)+V1X1 > (V1X1+V2X2+…+VnXn);

    該式子說明(X1,Y2,Y3,…,Yn)才是該01背包問題的最優解,這與最開始的假設(X1,X2,…,Xn)是01背包問題的最優解相矛盾,故01背包問題滿足最優性原理;

  f) 尋找遞推關系式,面對當前商品有兩種可能性:

    第一,包的容量比該商品體積小,裝不下,此時的價值與前i-1個的價值是一樣的,即V(i,j)=V(i-1,j);

    第二,還有足夠的容量可以裝該商品,但裝了也不一定達到當前最優價值,所以在裝與不裝之間選擇最優的一個,即V(i,j)=max{ V(i-1,j),V(i-1,j-w(i))+v(i) }

       其中V(i-1,j)表示不裝,V(i-1,j-w(i))+v(i) 表示裝了第i個商品,背包容量減少w(i)但價值增加了v(i);

    由此可以得出遞推關系式:

    1) j<w(i) V(i,j)=V(i-1,j)

    2) j>=w(i) V(i,j)=maxV(i-1,j)V(i-1,j-w(i))+v(i)

  g) 填表,首先初始化邊界條件,V(0,j)=V(i,0)=0;

技術分享圖片

  h) 然後一行一行的填表,

    1) 如,i=1,j=1,w(1)=2,v(1)=3,有j<w(1),故V(1,1)=V(1-1,1)=0;

    2) 又如i=1,j=2,w(1)=2,v(1)=3,有j=w(1),故V(1,2)=max{ V(1-1,2),V(1-1,2-w(1))+v(1) }=max{0,0+3}=3;

    3) 如此下去,填到最後一個,i=4,j=8,w(4)=5,v(4)=6,有j>w(4),故V(4,8)=max{ V(4-1,8),V(4-1,8-w(4))+v(4) }=max{9,4+6}=10;所以填完表如下圖:

技術分享圖片

技術分享圖片
 1 void FindMax()//動態規劃
 2 {
 3     int i,j;
 4     //填表
 5     for(i=1;i<=number;i++)
 6     {
 7         for(j=1;j<=capacity;j++)
 8         {
 9             if(j<w[i])//包裝不進
10             {
11                 V[i][j]=V[i-1][j];
12             }
13             else//能裝
14             {
15                 if(V[i-1][j]>V[i-1][j-w[i]]+v[i])//不裝價值大
16                 {
17                     V[i][j]=V[i-1][j];
18                 }
19                 else//前i-1個物品的最優解與第i個物品的價值之和更大
20                 {
21                     V[i][j]=V[i-1][j-w[i]]+v[i];
22                 }
23             }
24         }
25     }
26 }
技術分享圖片

  i) 表格填完,最優解即是V(number,capacity)=V(4,8)=10,但還不知道解由哪些商品組成,故要根據最優解回溯找出解的組成,根據填表的原理可以有如下的尋解方式:

    1) V(i,j)=V(i-1,j)時,說明沒有選擇第i 個商品,則回到V(i-1,j);

    2) V(i,j)=V(i-1,j-w(i))+v(i)實時,說明裝了第i個商品,該商品是最優解組成的一部分,隨後我們得回到裝該商品之前,即回到V(i-1,j-w(i));

    3) 一直遍歷到i=0結束為止,所有解的組成都會找到。

  j) 如上例子,

    1) 最優解為V(4,8)=10,而V(4,8)!=V(3,8)卻有V(4,8)=V(3,8-w(4))+v(4)=V(3,3)+6=4+6=10,所以第4件商品被選中,並且回到V(3,8-w(4))=V(3,3);

    2) 有V(3,3)=V(2,3)=4,所以第3件商品沒被選擇,回到V(2,3);

    3) 而V(2,3)!=V(1,3)卻有V(2,3)=V(1,3-w(2))+v(2)=V(1,0)+4=0+4=4,所以第2件商品被選中,並且回到V(1,3-w(2))=V(1,0);

    4) 有V(1,0)=V(0,0)=0,所以第1件商品沒被選擇;

技術分享圖片

  k) 到此,01背包問題已經解決,利用動態規劃解決此問題的效率即是填寫此張表的效率,所以動態規劃的時間效率為O(number*capacity)=O(n*c),由於用到二維數組存儲子問題的解,所以動態規劃的空間效率為O(n*c);

技術分享圖片
 1 void FindWhat(int i,int j)//尋找解的組成方式
 2 {
 3     if(i>=0)
 4     {
 5         if(V[i][j]==V[i-1][j])//相等說明沒裝
 6         {
 7             item[i]=0;//全局變量,標記未被選中
 8             FindWhat(i-1,j);
 9         }
10         else if( j-w[i]>=0 && V[i][j]==V[i-1][j-w[i]]+v[i] )
11         {
12             item[i]=1;//標記已被選中
13             FindWhat(i-1,j-w[i]);//回到裝包之前的位置
14         }
15     }
16 }
技術分享圖片

3、空間優化

  l) 空間優化,每一次V(i)(j)改變的值只與V(i-1)(x) {x:1...j}有關,V(i-1)(x)是前一次i循環保存下來的值;

  因此,可以將V縮減成一維數組,從而達到優化空間的目的,狀態轉移方程轉換為 B(j)= max{B(j), B(j-w(i))+v(i)}

  並且,狀態轉移方程,每一次推導V(i)(j)是通過V(i-1)(j-w(i))來推導的,所以一維數組中j的掃描順序應該從大到小(capacity到0),否者前一次循環保存下來的值將會被修改,從而造成錯誤。

技術分享圖片

技術分享圖片

  m) 同樣以上述例子中i=3時來說明,有:

    1) i=3,j=8,w(3)=4,v(3)=5,有j>w(3),則B(8)=max{B(8),B(8-w(3))+v(3)}=max{B(8),B(4)+5}=max{7,4+5}=9;

    2) j- -即j=7,有j>w(3),則B(7)=max{B(7),B(7-w(3))+v(3)}=max{B(7),B(3)+5}=max{7,4+5}=9;

    3) j- -即j=6,有j>w(3),則B(6)=max{B(6),B(6-w(3))+v(3)}=max{B(6),B(2)+5}=max{7,3+5}=8;

    4) j- -即j=5,有j>w(3),則B(5)=max{B(5),B(5-w(3))+v(3)}=max{B(5),B(1)+5}=max{7,0+5}=7;

    5) j- -即j=4,有j=w(3),則B(4)=max{B(4),B(4-w(3))+v(3)}=max{B(4),B(0)+5}=max{4,0+5}=5;

    6) j- -即j=3,有j<w(3),繼續訪問數組會出現越界,所以本輪操作停止,B(0)到B(3)的值保留上輪循環(i=2時)的值不變,進入下一輪循環i++;

技術分享圖片

  如果j不逆序而采用正序j=0...capacity,如上圖所示,當j=8時應該有B(8)=B(8-w(3))+v(3)=B(4)+5,然而此時的B(4)已經在j=4的時候被修改過了,原來的B(4)=4,現在B(4)=5,所以計算得出B(8)=5+5=10,顯然這於正確答案不符合;所以該一維數組後面的值需要前面的值進行運算再改動,如果正序便利,則前面的值將有可能被修改掉從而造成後面數據的錯誤;相反如果逆序遍歷,先修改後面的數據再修改前面的數據,此種情況就不會出錯了;

技術分享圖片
 1 void FindMaxBetter()//優化空間後的動態規劃
 2 {
 3     int i,j;
 4     for(i=1;i<=number;i++)
 5     {
 6         for(j=capacity;j>=0;j--)
 7         {
 8             if(B[j]<=B[j-w[i]]+v[i] && j-w[i]>=0 )//二維變一維
 9             {
10                 B[j]=B[j-w[i]]+v[i];
11             }
12         }
13     }
14 }
技術分享圖片

  n) 然而不足的是,雖然優化了動態規劃的空間,但是該方法不能找到最優解的解組成,因為動態規劃尋早解組成一定得在確定了最優解的前提下再往回找解的構成,而優化後的動態規劃只用了一維數組,之前的數據已經被覆蓋掉,所以沒辦法尋找,所以兩種方法各有其優點。

四、蠻力法檢驗:

  1) 蠻力法是解決01背包問題最簡單最容易的方法,但是效率很低

  2) (X1,X2,…,Xn)其中Xi=0或1表示第i件商品選或不選,共有n(n-1)/2種可能;

  3) 最簡單的方式就是把所有拿商品的方式都列出來,最後再做判斷此方法是否滿足裝包條件,並且通過比較和記錄找出最優解和解組成(如果滿足則記錄此時的價值和裝的方式,當下一次的裝法優於這次,則更新記錄,如此下去到最後便會找到最優解,同時解組成也找到);

  4) n件商品,共有n(n-1)/2種可能,故蠻力法的效率是指數級別的,可見效率很低;

  5) 蠻力法效率低不建議采取,但可以用於檢驗小規模的動態規劃解背包問題的正確性和可行性,如下圖輸出可見,解01背包問題用動態規劃是可行的:

技術分享圖片

五、總結:

  對於01背包問題,用蠻力法與用動態規劃解決得到的最優解和解組成是一致的,所以動態規劃解決此類問題是可行的。動態規劃效率為線性,蠻力法效率為指數型,結合以上內容和理論知識可以得出,解決此問題用動態規劃比用蠻力法適合得多。對於動態規劃不足的是空間開銷大,數據的存儲得用到二維數組;好的是,當前問題的解只與上一層的子問題的解相關,所以,可以把動態規劃的空間進行優化,使得空間效率從O(n*c)轉化為O(c),遺憾的是,雖然優化了空間,但優化後只能求出最優解,解組成的探索方式在該方法運行的時候已經被破壞掉;總之動態規劃和優化後的動態規劃各有優缺點,可以根據實際問題的需求選擇不同的方式。

01背包動態規劃