【算法分析】實驗 3. 基於動態規划方法求解0-1背包問題


實驗內容

    本實驗要求基於算法設計與分析的一般過程(即待求解問題的描述、算法設計、算法描述、算法正確性證明、算法分析、算法實現與測試),在針對0-1背包問題求解的實踐中理解動態規划 (Dynamic Programming, DP) 方法的思想、求解策略及步驟。

    作為挑戰:可以考慮基於跳躍點的改進算法,以及對連續型物品重量/背包容量的支持。

實驗目的

  • 理解動態規划方法的核心思想以及動態規划方法的求解過程;
  • 從算法分析與設計的角度,對0-1背包問題的基於DP法求解有更進一步的理解。

實驗結果

步驟1

    理解問題,給出問題的描述。

    n個物體,1個背包。對物品i,其價值為\(v_i\),重量為\(W_i\),背包的容量為 \(W\),如何選取物品,是的背包中裝入的物品的總價值最大?

    在約束條件為:選取物品的重量小於等於背包重量的情況下,盡可能讓背包中物品的總價值最大。

    根據問題描述,設計如下的約束條件和目標函數:

約束條件:

\[\begin{equation} \left\{ \begin{array}{**lr**} \sum_{i=1}^{n} w_ix_i \leq W \\ x_i \in \{0,1\}, 1 \leq i \leq n & \end{array} \right. \end{equation} \]

目標函數:

\[max\sum_{i=1}^{n} v_ix_i \]

    問題現在等價於,尋找一個在滿足約束條件情況下,並使目標函數達到最大的解 \(X=(x_1,x_2,...,x_n)\)

步驟2

    算法設計,包括策略與數據結構的選擇

    設計用二維數組對物品信息進行記錄: \(C[i][j]\) 用來記錄如果當前還有\(i\)個物品,背包容量還剩\(j\)的情況下,當前背包所能得到的最大價值。

很容易發現條件即:$$C[0][j] = C[i][0] = 0$$

遞歸定義應該為:

\[\\ C[i][j]= \begin{equation} \left\{         \begin{array}{**lr**}         C[i-1][j], & j < w_i \\ max\{C[i-1][j],C[i-1][j-w_i]+v_i\}, & j \ge w_i \end{array}   \right.   \end{equation} \]

    可以這樣理解,每個物品我可以選擇是否加入到背包中,首先判斷,當前物品是否重量已經大於背包所能容納的重量;如果能容納該物體,則進行判斷加入該物品\(C[i-1][j-w_i]+v_i\)得到的價值更高,還是不加入該物品\(C[i-i][j]\)所能得到的物品的總價值更高。

步驟3

    描述算法。希望采用源代碼以外的形式,如偽代碼或流程圖等;

偽代碼表示:

01PACKAGE(n,w,v,W) // n為物品的個數,w為重量, v為價值,W為背包容量
	// C[1..n,1..n]為最優解
	for i=1 to n:
		do C[i][0] = 0
	for j=1 to W:
		do C[0][j] = 0
	for i=1 to n:
		for j=1 to W:
			do
			if j < w[i]:
			then C[i][j]=C[i-1][j]
			else
			then C[i][j]=max{C[i-1][j],C[i-1][j-w[i]]+v[i]}
	return C

步驟4

    算法的正確性證明。需要這個環節,在理解的基礎上對算法的正確性給予證明;

對該算法進行最優子結構的證明:

假設\(X=(x_1,x_2,x_3,...,x_n)\)是背包問題的最優解,那么\((x_2,..,x_n)\)是下面問題的一個最優解:

\[\begin{equation} \left\{ \begin{array}{**lr**} \sum_{i=2}^{n} w_ix_i \leq W-w_1x_1 \\ x_i \in \{0,1\}, 2 \leq i \leq n & \end{array} \right. \end{equation} \]

目標函數: $$max\sum_{i=2}^nv_ix_i$$

即除去第一個物品以后的子問題

證明如下:(反證法)

\(X=(x_2,...,x_n)\)不是上述子問題的最優解,設\(Y=(y_2,...,y_n)\)是上述問題的最優解,則\(Y\)所求的目標函數的值一定比X求得的目標函數的值更大,即:

\[\sum_{i=2}^nv_iy_i > \sum_{i=2}^nv_ix_i \]

\(Y\)滿足約束條件:\(\sum_{i=2}^nw_iy_i \leq W-w_1x_1\),即 \(w_1x_1+\sum_{i=2}^nw_iy_i \leq W\),該不等式證明\((x_1,y_1,y_2,...,y_n)\)是原問題的一個解。

在公式 \((5)\) 中左右同時加上\(v_1x_1\),可得:$$ v_1x_1+\sum_{i=2}^nv_iy_i > v_1x_1+\sum_{i=2}^nv_ix_i$$,說明\((x_1,y_1,y_2,...,y_n)\)要比\((x_1,x_2,...,x_n)\) 方案價值更高,所以\((x_1,x_2,...,x_n)\)不是最優解,產生了矛盾。

所以其最優子結構的性質得證。

步驟5

    算法復雜性分析,包括時間復雜性和空間復雜性;

  • 求解0-1背包問題部分

時間復雜性分析:

由於只需要遍歷進行,所以只需要考慮循環中的復雜度即可。

\[T(n) =O(n)+O(W)+O(nW) \\ =O(nW) \]

空間復雜度,主要是生成數組時占用的空間。

\[T(n)=O(n^2) \]

  • 得到最優解部分

時間復雜度分析:

只需要一個循環即可。

\[T(n)=O(n) \]

空間復雜度為:$$O(1)$$

步驟6

    算法實現與測試。附上代碼或以附件的形式提交,同時貼上算法運行結果截圖;

# -*- coding: utf-8 -*-
"""
Created on Fri Sep 28 12:44:40 2018
@theme: 算法准備-01背包問題
@author: pprp
"""

import numpy as np

def solvePackage(n,w,v,W):
    """solve the 01 package problem"""

    C=np.zeros((n+1,W+1))
    
    for i in range(n):
        C[i][0]=0
    for i in range(W):
        C[0][i]=0

    for i in range(1,n+1):
        for j in range(1,W+1):
            if j < w[i-1]:
                C[i][j]=C[i-1][j]
            else:
                C[i][j]=max(C[i-1][j],C[i-1][j-w[i-1]]+v[i-1])
    return C
        
def getSolution(n,w,W,C):
    j = W
    x = np.zeros(n)
    for i in range(n,0,-1):
        if C[i][j]==C[i-1][j]:
            x[i-1] = 0
        else:
            x[i-1] = 1
            j -= w[i-1]
    return x
if __name__ == "__main__":
    n = 11
    w = np.array([2, 6, 3, 4, 2, 8, 2, 4, 7, 5, 1])
    v = np.array([10,23,5,34,23,17,22,32,12,15,32])
    W = 15

    C=solvePackage(n,w,v,W)
    x=getSolution(n,w,W,C)

    print("packages:",x)

    print("Output:\n",C)

實驗結果

packages: [1. 0. 0. 1. 1. 0. 1. 1. 0. 0. 1.]

驗證結果:10+34+23+22+32+32=153

實驗總結

動態規划基本思想

    動態規划算法通常是用來解決某種最優性質的問題。基本思想是將帶求解問題划分為若干個子問題,先求解子問題,然后從子問題的解得到原問題的解。動態規划與分治法的區別在於,動態規划的子問題可能是互相重疊的重復計算的,分治法則是相互獨立的。可以用一個表來記錄子問題是否已經求解,這樣可以避免重復求解。

動態規划應用條件

    需要滿足最優化原理、無后效性和重疊性。

  • 最優化原理,一個最優化策略的子策略一定是最優的,就是滿足最優子結構的性質。

  • 無后效性,一個階段以前各階段的狀態無法直接硬性它未來的決策,只能通過當前的這個狀態。

  • 重疊性,就是記錄已經解決過的問題,需要存儲已經解決過的問題,空間復雜度比較大,是一種以空間換時間的算法。

難點

    動態規划的難點在於,如何根據問題的最優子結構的性質,構造動態規划方法中的遞歸公式或動態規划方程。就比如本問題中,如何設計這個方程才是難點所在。

遇到的問題

    在逆向求解使用的哪幾個背包的時候,由於對問題理解的不深刻,導致匯總的時候發現計算結果出現了問題,也就是\(getSolution\)這個函數出現了問題。應該逆向進行求解問題,也就是從后往前進行推導,更改了循環的方向以后就可以得到最終的結果了。

心得

    只有在真正理解算法的基礎上,然后加以偽代碼的梳理,這時候寫才能一氣呵成。另外還需要對代碼計算出來的結果進行人工核查,防止某些問題被忽略掉。另外這個問題已經被老師分析的比較透徹,所以寫起來沒有太大的困難。但是遇見新的問題的時候,如何構造解決問題的方法才是難點所在。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM