天天看點

背包問題不同算法對比-動态規劃,回溯法,貪婪法

文章目錄

    • 1 背包問題定義
    • 2 回溯法
      • 2.1 回溯法的設計思想
      • 2.2 回溯法求解0/1背包問題
      • 2.3 算法步驟
      • 2.4 代碼實作
      • 2.5 實驗結果
        • 2.5.1 回溯法求解背包問題的搜尋樹
        • 2.5.2 搜尋樹生成過程
        • 2.5.3實際輸出:
      • 2.6 回溯算法的效率分析
    • 3 貪婪算法
      • 3.1 貪婪法的設計思想
      • 3.2 貪婪法解決部分背包問題
        • 3.2.1 算法設計:
        • 3.2.2 實驗結果
      • 3.3貪婪算法解決0/1背包問題
        • 3.3.1 算法設計
        • 3.3.2 實驗結果
    • 4 動态規劃算法
      • 4.1 動态規劃的設計思想
      • 4.2 動态規劃法求解0/1背包問題
      • 4.3 算法步驟
      • 4.4 代碼實作
      • 4.5 優化算法
      • 4.6 實驗結果
    • 5 動态規劃法和貪婪法對比
    • 6 總結

1 背包問題定義

​ 給定一個載重量為M的背包,還有n個重量為wi,價值為pi的物體,1<=i<=n,要求把物體裝滿背包,并且使得背包内物體的價值最大,這類問題稱為背包問題。背包問題還可劃分成兩類,一類是0/1背包問題:物體不可分割,隻能完整的裝入背包,另一類是非0/1問題:物體可以分割成部分裝入背包。本文将用三種算法求解背包問題,并進行對比,三種算法分别是:回溯法,貪婪法和動态規劃法。

2 回溯法

2.1 回溯法的設計思想

​ 回溯法在确定了解空間的結構後,從根結點出發,以深度優先的方式搜尋整個解空間,此時根結點成為一個活結點,并且成為目前的擴充結點。每次都從擴充結點向縱向搜尋新的結點,當算法搜尋到了解空間的任一結點,先判斷該結點是否肯定不包含問題的解(是否還能或者還有必要繼續往下搜尋),如果确定不包含問題的解,就逐層回溯;否則,進入子樹,繼續按照深度優先的政策進行搜尋。當回溯到根結點時,說明搜尋結束了,此時已經得到了一系列的解,根據需要選擇其中的一個或者多個解即可。

回溯法解決問題一般分為三個步驟:
           
  1. 針對所給問題,定義問題的解空間。
  2. 确定易于搜尋的解空間結構。
  3. 以深度優先的方式搜尋解空間。

2.2 回溯法求解0/1背包問題

​ 假設:xi是物體i被裝入背包的部分,xi =0,1,當xi=0時表示物體i沒有被裝入背包;當xi=1時表示物體i被全部裝入背包。根據問題的要求,可以構造下面的限制方程和目标函數:

KaTeX parse error: Unknown column alignment: * at position 16: \begin{array}{*̲{20}{l}}{{\math…

​ 于是問題可以歸結為尋找一個滿足限制方程(1),并使得目标函(2)達到最大值的解向量 X=(x1,x2,x3,x4,…,xn)。回溯法搜尋這個解向量X時,狀态空間樹是一個高度為n的完全二叉樹,其節點總數是2^(n+1) -1.從根節點到葉子節點的所有路徑,描述了問題解的所有可能狀态。

​ 首先約定:第i層的左兒子子樹描述物體vi被裝入背包的情況,右兒子子樹描述物體vi未被裝入背包的情況。

​ 在狀态空間樹的搜尋過程中,一方面可以通過限制方程(5)來控制不需要通路的節點,另一方面還可以利用目标函數(6)來進一步控制不需要通路的節點的個數。初始化時把目标函數的上界初始化為0,把物體按價值重量比的非增順序排列,然後按照這個順序搜尋;盡量沿着左兒子節點前進,當不能沿着左兒子節點繼續前進時,說明得到了問題的一個部分解。然後把搜尋轉移到右兒子子樹。此時,估計這個部分解所能得到的最大價值,把該值和目前的上界進行對比,如果該值大于目前上界,就繼續由右兒子樹向下搜尋,擴大這部分解,直到找到一個可行解。最後把可行解儲存起來,用目前可行解的值重新整理目标函數的上界,并向上回溯,尋找其他可行解;如果由部分解所估計的最大值小于目前上界,就丢棄目前正在搜尋的部分解,直接向上回溯。

2.3 算法步驟

​ 假設目前的部分解是{x0,x1,…,xk-1},同時有:

KaTeX parse error: Unknown column alignment: * at position 17: …{\begin{array}{*̲{20}{l}} {{\mat…

​ 式子(7)表示裝入第k個物體之前,背包尚有空餘的載重量,繼續裝入物體k之後,将超過背包的載重量。由此得到部分解{x0,x1,…,xk},其中xk=0。由這個部分解繼續向下搜尋将有:

KaTeX parse error: Unknown column alignment: * at position 17: …{\begin{array}{*̲{20}{l}} {{\mat…

​ 式子(8)表示,不裝入第k個物體,繼續裝入k+1,k+2…k+m-1的物體,背包尚有空餘的載重量,但是繼續裝入第k+m個物體之後,将超過物體的載重量。因為物體是按照價值重量比按非增順序排列的,是以這個部分解還可以繼續向下搜尋。能找到的可能解的最大值不會超過:

∑ i = 0 k x i p i + ∑ i = k + 1 k + m − 1 x i p i + ( M − ∑ i = 0 k x i w i − ∑ i = 0 k + m − 1 x i w i ) × p k + m / w k + m ( 9 ) {{\mathop{ \sum }\limits_{{i=0}}^{{k}}{\mathop{{x}}\nolimits_{{i}}\mathop{{p}}\nolimits_{{i}}}}+{\mathop{ \sum }\limits_{{i=k+1}}^{{k+m-1}}{\mathop{{x}}\nolimits_{{i}}\mathop{{p}}\nolimits_{{i}}}}+{ \left( {M-{\mathop{ \sum }\limits_{{i=0}}^{{k}}{\mathop{{x}}\nolimits_{{i}}\mathop{{w}}\nolimits_{{i}}}}-{\mathop{ \sum }\limits_{{i=0}}^{{k+m-1}}{\mathop{{x}}\nolimits_{{i}}\mathop{{w}}\nolimits_{{i}}}}} \right) } \times \mathop{{p}}\nolimits_{{k+m}}/\mathop{{w}}\nolimits_{{k+m}}}(9) i=0∑k​xi​pi​+i=k+1∑k+m−1​xi​pi​+(M−i=0∑k​xi​wi​−i=0∑k+m−1​xi​wi​)×pk+m​/wk+m​(9)

​ 如果目前搜尋到目前結點,它的最大價值估計值小于目前目标函數上界,則放棄向下搜尋,進行回溯。如果目前結點是左兒子子樹結點,則轉向相應的右兒子子樹結點進行搜尋;如果目前結點是右兒子子樹結點,則退回父節點。

用回溯法求解0/1背包問題可以概括為以下10個步驟:

  1. 把物體按價值重量比按照遞減的順序排列。
  2. 把w_cur(目前背包物體的總重量)、p_cur(目前背包物體的總價值)和p_total(所有可行解的最大價值)初始化為0,解向量的各個分量初始化為0,搜尋樹的搜尋深度k置為0 。
  3. 令p_est(目前部分解可能達到的最大價值的估計值) = p_cur,對滿足k<=i<n的所有i,按照式子(8)(9)更新從目前的部分解可取的的最大價值估計值p_est。
  4. 如果p_est > p_total,轉步驟5,否則轉步驟8.
  5. 從vk開始把物體裝入背包,直到沒有物體可裝入或者裝不下物體vi為止,并生成部分解yk,…,yi,k<=i<n,重新整理p_cur。
  6. 如果i>=n-1,則得到一個新的可行解,用可行解更新解向量X,更新p_total=p_cur。轉步驟3,以便回溯到其他可行解,否則得到一個部分解,轉步驟7
  7. 令k=i+1,舍棄物體vi,轉步驟3,以便可以從物體v(k+1)繼續裝入。
  8. 當i>=0并且yi=0時,執行i=i-1,直到yi=1為止;即沿着右兒子分支節點方向向上回溯,直到回到相應的左兒子分支節點。
  9. 如果i<0,算法結束,否則轉步驟10 。
  10. 令yi=0,w_cur=w-cur - wi,p_cur = p_cur - pi,k=i+1,轉步驟3 。

2.4 代碼實作

typedef struct {
	float w;        /*物體重量*/ 
	float p;        /*物體價值*/ 
	float v;        /*物體價值重量比*/ 
} Object; 
Object ob[n];       /*n個物體下資訊*/
float M;			/*背包載重量*/ 
int x[n];			/*可能的向量解*/ 
int y[n];			/*目前搜尋的解向量*/
float p_eat;		/*目前搜尋方向部分解的最大價值估計值*/
float p_total;
float w_cur;
float p_cur;

           
float knapsack_back(Object ob[],float M,int n,int x[]) {
	float w_cur, p_total,p_cur,w_eat,p_eat;
	int *y = new int[n+1];
	for (int i = 0; i < n; i++) {
		ob[i].v = ob[i].p /ob[i].w;
		y[i] = 0;
	} 
	merge_sort(ob, n);						/*物體按照價值重量比的非增順序排列*/
	w_cur = p_cur = p_total = 0;
	y[n] = 0;
	k = 0;
	while (k<=n) {
		w_est = w_cur;
		p_est = p_cur;
		for (int i = k; i< n;i++) {			/*沿着目前分支搜尋*/
			w_est += ob[i].w;
			if (w_est<M) {
				p_est += ob[i].p; 
			} else {
				p_est += (M - w_est+ob[i].w)/ob[i].w * ob[i].p;
				break;
			}
	    }
		if (p_est > p_total) {				/*估計值大于上界*/
			for (int i = k;i<n; i++) {
				if (w_cur + ob[i].w <= M) {	/*可裝入第i個物體*/ 
					w_cur += ob[i].w;
					p_cur += ob[i].p;
					y[i] = 1;
				} else {
					y[i] = 0;				/*不能裝入第i個物體*/ 
					break;
				}
			}
			if(i >= n-1) {					/*n個物體已經全部裝入*/     
			if (p_cur > p_total) {
				p_total = p_cur;			/*重新整理目前上限*/ 
				k = n;
				for (int j = 0; j < n; j++)	/*儲存可能的解*/ 
					x[j] = y[j];
			}
		} else 
            k = i+1;						/*繼續裝入其他物體*/ 
		} else {							/*估計值小于目前上限*/
			while ((k >= 0) && (y[k] != 0)) /*沿着右分支節點方向回溯*/
				k = k-1;					/*直到左分支節點*/ 
			if (k<0) break;					/*到根節點,則結束*/ 
			else {
				w_cur = w_cur - ob[k].w;
				p_cur = p_cur - ob[k].p;
				y[k] = 0; k += 1;
			} 
		}	
    }
	delete y;
	return p_total; 
}
           

2.5 實驗結果

​ 輸入:物體數量為5,背包載重量為50,背包的價值分别是(40,45,44,50,45),背包的重量分别是(10,15,20,25,30)。

​ 理論上輸出:背包最大價值139,裝入背包的物體為(0,1,1,1,0)。

2.5.1 回溯法求解背包問題的搜尋樹

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-G2Evpl6c-1580817786560)(C:\Users\86188\AppData\Roaming\Typora\typora-user-images\image-20200107174612211.png)]

2.5.2 搜尋樹生成過程

  1. 開始時,目标函數的上界p_total初始化為0,計算從根節點開始搜尋所取得的最大估計值p_est=159, 大于p_total,是以向下生成節點1,2,3,4,得到部分解(1,1,1,0)。
  2. 結點4是右兒子分支結點,于是估計從結點4開始向下搜尋可取得的最大值p_est=151.5,大于p_total,是以繼續向下搜尋生成結點5,得到最大價值129,更新p_total=129,将可行解=(1,1,1,0,0)儲存在解向量X中。
  3. 由葉子結點5繼續搜尋,估計可能取得的最大值為129,不大于p_total,是以向上回溯k–,直到yk=1到達結點3,并生成相應的右兒子分支結點6,得到部分解(1,1,0)。
  4. 計算從結點6開始搜尋可取得的最大價值估計值p_est=155,大于p_total,是以向下繼續搜尋生成結點7、8。并生成最大價值p_cur=135,取得的可行解為(1,1,0,1,0),并更新p_total = p_cur=135。同時用這個可行解更新解向量X中的内容。
  5. 由結點8繼續搜尋,計算其最大價值估計值為135,不大于p_total,是以向上回溯,直到yk=1,回到結點7,并生成相應的右兒子分支結點9,得到部分解(1,1,0,0)。
  6. 計算從結點9搜尋所取得的最大價值估計值為137.5,大于p_total=135,是以繼續向下搜尋生成結點10,得到最大價值p_cur=130,小于p_total的值,是以p_total和解向量X均未更新。因為結點10是左兒子結點,是以向前回溯後生成其相應的右兒子分支結點11,得到最大價值為85,小于p_total的值,是以p_total和解向量X均未更新。
  7. 由葉子結點11繼續搜尋,計算其最大價值估計值為85,小于p_total,是以沿着右兒子分支結點的方向向上回溯,直到結點2,生成其相應的右兒子分支結點12,得到部分解(1,0)。
  8. 計算從結點12向下搜尋可取得的最大價值估計值為190,是以向下搜尋生成結點13,14,15。得到最大價值為134,小于p_total,是以解向量X和p_total值均未更新。
  9. 由葉子結點15繼續搜尋,計算最大價值估計值為134,小于p_total,是以沿着右兒子分支結點方向向上回溯,到達結點13,生成其對應的右兒子分支結點16,由于結點16是右分支結點,計算其最大價值估計值為129,小于p_total ,是以沿着右兒子分支結點向上回溯,到達結點13,生成其對應的右兒子分支結點17,計算從結點17繼續搜尋可取得的最大價值估計值為140,是以可以沿着結點17向下搜尋,生成結點18,19。得到的最大價值p_cur為90,小于p_total,是以解向量X和p_est均未更新。
  10. 由葉子結點19繼續搜尋,計算可取得的最大價值估計值p_est為90,小于p_total,是以沿着右兒子分支結點方向向上回溯,直到結點18,生成其相應的右兒子結點20,計算從結點20開始搜尋可取得的最大價值估計值p_est=115,小于p_total,是以繼續向上回溯,回到結點1,生成其相應的右兒子結點21。計算從結點21繼續搜尋可取得的最大價值估計值為180,是以繼續向下搜尋生成結點22、23、24、25。得到最大價值為139,大于p_total,是以更新p_total=139,得到可行解(0,1,1,1,0),用該可行解更新解向量X。
  11. 由葉子結點25繼續搜尋,計算可取得的最大價值估計值為139,不大于p_total,是以向上回溯回到結點24,生成其相應的右兒子分支節點26,計算從結點26搜尋可取得的最大價值估計值為131.5小于p_total,是以繼續向上回溯,回到結點23,生成其對應的右兒子分支結點27,計算從結點27搜尋可取得的最大價值估計值為115小于p_total,是以繼續向上回溯,回到結點22,生成其對應的右兒子分支結點28,計算從結點28搜尋可取得的最大價值估計值為132小于p_total,是以繼續向上回溯,回到結點0.算法結束。

2.5.3實際輸出:

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-HfHbqeZw-1580817786561)(C:\Users\86188\AppData\Roaming\Typora\typora-user-images\image-20200107202953581.png)]

​ 實驗證明,回溯法可以求得0/1背包問題的最優解,但是它的時間複雜度和空間複雜度在最壞的情況下為O(2^n)。

2.6 回溯算法的效率分析

​ 回溯算法的效率和以下幾個因素有關:

  • 生成節點所花費的時間
  • 計算限制方程所花費的時間
  • 計算目标函數的界所花費的時間
  • 所生成節點的個數

​ 回溯算法的運作時間取決于搜尋過程中它生成的結點數。不同的搜尋方式所生成的結點數是不同的。用回溯算法求解背包問題時,最壞的情況下,所搜尋的結果是一棵滿二叉樹,時間複雜度為o(2^n),每次還要對是否将n個物體裝入背包進行比較,時間複雜度為O(n), 是以最壞情況下回溯算法是時間複雜度是O(n*2^n)。

3 貪婪算法

3.1 貪婪法的設計思想

​ 貪婪法一般由一個疊代的循環組成,在每一輪循環中,通過少量的局部的計算,尋找出一個局部最優解。是以它是通過一步步地尋找局部最優解來試圖建立問題的最終解。貪婪法的每一步的工作都增加了部分解的規模,每一步的選擇也都極大增長了它所希望實作的目标函數。正因如此,在很多執行個體中,利用貪婪算法所産生的局部最優解最終都能建立問題的全局最優解。

貪婪法有兩個很重要的性質:

  1. 貪婪選擇性質

    所謂貪婪選擇性質,是指所求問題的最優解可以通過一系列局部最優解的選擇來達到。每進行一次選擇就能得到一個局部的解,把所求問題的解簡化成一個規模更小的類似子問題。

  2. 最優子結構性質

    最優子結構是指一個問題的最優解包含其子問題的最優解。

3.2 貪婪法解決部分背包問題

​ 背包問題分為兩種情況,一種情況是每個物體都是完整的裝入背包即0/1背包問題,另一種情況是物體可以分部分裝入背包,本節讨論的正是第二種情況。

​ 假設:xi是物體i被裝入背包的部分,0 <= xi <= 1,當xi=0時表示物體i沒有被裝入背包;當xi=1時表示物體i被全部裝入背包。根據問題的要求,可以構造下面的限制方程和目标函數:

KaTeX parse error: Unknown column alignment: * at position 16: \begin{array}{*̲{20}{l}} {{\mat…

​ 于是問題可以歸結為尋找一個滿足限制方程(1),并使得目标函(2)達到最大值的解向量 X=(x1,x2,x3,x4,…,xn)。

​ 解決方案是:優先選擇價值pi最大的物體裝入背包,直到最後一個物體裝不下時,選擇一個适當的物體分割成部分xi<1裝入背包。但是采取這種方法不一定能達到最優解,因為如果選擇的物體的重量很大,那麼背包很快就被裝滿,使得其他價值高的物體無法繼續裝入,最終背包的價值并不是最大的。是以,本文所選擇的政策都是先計算每個物體的價值重量比,然後将比值大的物體優先裝入背包。

3.2.1 算法設計:

​ 首先定義物體的結構體:

typedef struct {
float p;          /*物體的價值*/
float w;          /*物體的重量*/
float v;          /*物體的價值重量比*/
}
           

​ 貪心算法的核心代碼如下:

float knapsack_greedy(float M, OBJECT ob[],float x[], int N) {
	float m,p=0;
	int n = N;
	for (int i = 0; i < n; i++) {
		ob[i].v = ob[i].p /ob[i].w;
		x[i] = 0;
	} 
	merge_sort(ob, n);				/*重新給物體排序,價值重量比大的物體排在前面*/
	m = M;
	for (int i = 0; i < n; i++) {
		if (ob[i].w <= m) {
			x[i] = 1; 
			m -= ob[i].w;
			p += ob[i].p;
		} else {
            x[i] = m/ob[i].w;		/*當最後一個物體重量比背包剩餘載重量大時,将物體劃分成一定比例裝入背包*/
            p += x[i]*ob[i].p;
			break;
		}
	}
	return p;
}
           

3.2.2 實驗結果

​ 輸入:物體數量為5,背包載重量為60,背包的價值分别是(40,45,44,50,45),背包的重量分别是(10,15,20,25,30)。

​ 理論上輸出:背包最大價值159,裝入背包的物體為(1,1,1,0.6,0)。

​ 實際輸出:

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-jIPC8Ed4-1580817786561)(C:\Users\86188\AppData\Roaming\Typora\typora-user-images\image-20200107114432242.png)]

​ 輸入:物體數量為3,背包載重量為50,背包的價值分别是(60,100,120),背包的重量分别是(10,20,30)。

​ 理論上輸出:背包最大價值240,裝入背包的物體為(1,1,0.67)。

​ 實際輸出:

​ [外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-Telqk8DO-1580817786561)(C:\Users\86188\AppData\Roaming\Typora\typora-user-images\image-20200106162748602.png)]

​ 從實驗結果可以看出,按照貪婪法的運算步驟可以找到背包問題的最優解。

3.3貪婪算法解決0/1背包問題

​ 假設:xi是物體i被裝入背包的部分,xi =0,1,當xi=0時表示物體i沒有被裝入背包;當xi=1時表示物體i被全部裝入背包。根據問題的要求,可以構造下面的限制方程和目标函數:

KaTeX parse error: Unknown column alignment: * at position 16: \begin{array}{*̲{20}{l}}{{\math…

​ 于是問題可以歸結為尋找一個滿足限制方程(1),并使得目标函(2)達到最大值的解向量 X=(x1,x2,x3,x4,…,xn)。

3.3.1 算法設計

​ 定義資料結構:

typedef struct {
float p;          /*物體的價值*/
float w;          /*物體的重量*/
float v;          /*物體的價值重量比*/
}
           

​ 輸入:背包載重量M,物體的價值p,物體的重量w,物體的個數n。

​ 輸出:n個物體被裝入背包的分類x[],背包中物體的總價值。

​ 核心代碼如下:

float knapsack_greedy(float M, OBJECT ob[],float x[], int N) {
	float m,p=0;
	int n = N;
	for (int i = 0; i < n; i++) {
		ob[i].v = ob[i].p /ob[i].w;
		x[i] = 0;
	} 
	merge_sort(ob, n);				/*重新給物體排序,價值重量比大的物體排在前面*/
	m = M;
	for (int i = 0; i < n; i++) {
		if (ob[i].w <= m) {
			x[i] = 1; 
			m -= ob[i].w;
			p += ob[i].p;
		} else {					/*當最後一個物體重量大于背包剩餘載重量時,不再繼續放入物體*/
			break;
		}
	}
	return p;
}
           

3.3.2 實驗結果

​ 輸入:物體數量為5,背包載重量為60,背包的價值分别是(40,45,44,50,45),背包的重量分别是(10,15,20,25,30)。

​ 理論上輸出:背包最大價值139,裝入背包的物體為(0,1,1,1,0)。

​ 實際輸出:

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-k4UweA3j-1580817786563)(C:\Users\86188\AppData\Roaming\Typora\typora-user-images\image-20200107114702514.png)]

​ 輸出的129并不是背包能裝入的最大價值,如果隻裝重量為12,20和25的物體,那麼背包價值是可達139,比用貪婪法求得的價值要高很多。是以貪心算法不能解決0/1背包問題。

4 動态規劃算法

4.1 動态規劃的設計思想

​ 動态規劃和分治算法類似,其基本思想就是将問題劃分為若幹個子問題,先求解子問題,然後從這些子問題的解得到原問題的解。

​ 動态規劃算法的有兩個重要的性質:

  1. 最優子結構性質:即問題的最優解包含子問題的最優解。由每一步的子問題的最優解最終生成問題的最優解。
  2. 重疊子問題性質:動态規劃求解問題時,每次産生的子問題并不總是新的子問題,有些子問題被重複計算多次,這種性質稱為重疊子問題。解決方法:将已求得的子問題的解儲存起來,避免重複計算相同的子問題。

4.2 動态規劃法求解0/1背包問題

​ 假設:xi是物體i被裝入背包的部分,xi =0,1,當xi=0時表示物體i沒有被裝入背包;當xi=1時表示物體i被全部裝入背包。根據問題的要求,可以構造下面的限制方程和目标函數:

KaTeX parse error: Unknown column alignment: * at position 16: \begin{array}{*̲{20}{l}}{{\math…

​ 于是問題可以歸結為尋找一個滿足限制方程(1),并使得目标函(2)達到最大值的解向量 X=(x1,x2,x3,x4,…,xn)。

​ 這個問題也可以用動态規劃分階段決策的方法,來确定把哪個物體裝入背包的最優決策。構造下面的動态規劃函數:

KaTeX parse error: Unknown column alignment: * at position 17: …{\begin{array}{*̲{20}{l}} {{\mat…

​ 式子(3)表明把前面i個物體裝入載重為0 的背包,或者把0個物體裝入載重為j的背包,其價值都為0.式子(4)的第一個式子表明:如果第i個物體裝入載重量為j的背包,則背包裝入前i個物體的總價值和裝入前i-1個物體的總價值一樣。(4)的第二個式子表明,如果第i個物體的重量小于背包的載重量,則背包裝入前i個物體的總價值等于将前i-1個物體裝入載重量為(j-wi)的背包所得到的價值量加上物體i的價值pi。如果第i個物體沒有裝入背包,則背包中的物體價值等于将前i-1個物體裝入載重量為j的背包所得的價值。

4.3 算法步驟

​ 設optpn(m)是載重量為m的背包,裝入n個物體時得到的最大價值。為了确定裝入背包的具體物品,從optpn(m)的值向前倒推,如果optpn(m)大于optp(n-1)(m),說明第n個物品被裝入了背包,則下一步确定把前n-1個物體裝入載重量為m-wn的背包中;如果optpn(m)小于等于optp(n-1)(m),說明第n個物品沒有被裝入了背包,則下一步确定把前n-1個物體裝入載重量為m的背包中。

​ 算法具體步驟如下:

  1. 初始化,對滿足0<=i<=n,0<=j<=m的i和j,令式子(3)成立。
  2. 令i=1.
  3. 對滿足0<=j<=m的j,按照式子(4)計算optpi(j)。
  4. i=i+1,若i>n,轉步驟5,否則轉步驟3。
  5. 令i=n,j=m。
  6. 按照

    KaTeX parse error: Unknown column alignment: * at position 17: …{\begin{array}{*̲{20}{l}} {\begi…

求第i個分量xi

  1. i=i-1,若i>0,則轉步驟(6),否則算法結束。

4.4 代碼實作

//動态規劃表
 	int optp[N][N];
 	for (int i = 0; i <= n; i++) {
 		optp[i][0] = 0;
 		x[i] = 0;
	 }
 	for (int j = 0; j <= M; j++) optp[0][j] = 0;
 	
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= M; j++) {
			if (j < w[i])
				optp[i][j] = optp[i - 1][j];
			else {
				optp[i][j] = max(optp[i - 1][j], optp[i - 1][j - w[i]] + p[i]);
				
			}
				
		}
	}
           

​ 時間複雜度為O(n*m)

4.5 優化算法

​ 由狀态轉移方程(4)可知,optp[i] [j]的值僅僅和optp[i] [j-w[i]]和optp[i-1] [j]的值相關,是以可以将上述的二維矩陣優化成為一維矩陣,減少空間複雜度。優化之後,狀态轉移方程變為optp[i] [j] = max ( optp[i] [j-w[i]], optp[i-1] [j] )。

​ 核心代碼如下:

//優化動态規劃
 	int optp[M];
 	for (int i = 0; i <= M; i++) {
 		optp[i] = 0;
	 }
 	
	for (int i = 1; i <= n; i++) {
		for (int j = M; j >= w[i]; j--) {
			
				optp[j] = max(optp[j], optp[j - w[i]] + p[i]);
				
			}
				
		}
	}
           

​ 優化之後,算法的時間複雜度和空間複雜度都是O(n*m),而空間複雜度優化為O(m)。

4.6 實驗結果

​ 輸入:物體數量為5,背包載重量為10,背包的價值分别是(6,3,5,4,6),背包的重量分别是(2,2,6,5,4)。

​ 理論上輸出:背包最大價值15,裝入背包的物體為(1,1,0,0,1)。

​ 動态規劃的計算步驟表如下:

1 2 3 4 5 6 7 8 9 10
1 6 6 6 6 6 6 6 6 6
2 6 6 9 9 9 9 9 9 9
3 6 6 9 9 9 9 11 11 14
4 6 6 9 9 9 10 11 13 14
5 6 6 9 9 12 12 15 15 15

​ 實際輸出:

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-TDBkw0XN-1580817786564)(C:\Users\86188\AppData\Roaming\Typora\typora-user-images\image-20200106195740745.png)]

​ 輸入:物體數量為5,背包載重量為60,背包的價值分别是(40,45,44,50,45),背包的重量分别是(10,15,20,25,30)。

​ 理論上輸出:背包最大價值139,裝入背包的物體為(0,1,1,1,0)。

​ 實際輸出:

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-hglMDrl7-1580817786564)(C:\Users\86188\AppData\Roaming\Typora\typora-user-images\image-20200107115212809.png)]

​ 上述實驗證明,動态規劃方法可以求解0/1背包問題的最優解。

5 動态規劃法和貪婪法對比

​ 共同點:問題具有最優子結構特征,能從子問題的最優解找到原問題的最優解。

​ 不同點:

  1. 動态規劃方法尋找問題最優解時依賴所有子問題的解。而貪心算法從一個局部解開始擴充,作出最優選擇後得到一個新的子問題,所作的選擇隻依賴過去所作的選擇,直到找到整體最優解。
  2. 貪婪法産生的結構圖,所作的選擇隻依賴于過去所作的選擇:

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-RsWF4z6R-1580817786564)(C:\Users\86188\Desktop\貪心算法.png)]

​ 動态規劃産生的結構圖,它依賴于所有子問題的解:

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-WbP1qvQk-1580817786565)(C:\Users\86188\Desktop\動态規劃.png)]

  1. 貪心算法的時間複雜度和空間複雜度都是O(n^2),空間複雜度可以優化為O(n);動态規劃的時間複雜度是O(n*m),空間複雜度可以優化為O(m)。
  2. 貪心算法不能求解0/1背包問題,隻能求解部分背包問題。而動态規劃法既可以求解部分背包問題,也可以求解0/1背包問題。

6 總結

​ 背包問題的三個算法對比,貪婪法隻能解決部分背包問題,不能解決0/1背包問題,因為用貪婪法每次裝入價值高的物體,背包很快就不能繼續裝入其他物體,最終的總價值并不是最大的。但是貪婪法在選擇背包之前要對物體的價值重量比進行排序,是以貪婪法的時間複雜度是O(n^2)。動态規劃法既能解決部分背包問題也能解決0/1背包問題,而且動态規劃法在選擇物體裝入時,不需要對物體的價值重量比進行排序,時間複雜度為O(n*m)。回溯法在求解背包問題上效率并不高。

繼續閱讀