java數據結構與算法總結(六)--0-1揹包問題

原文來自

只瞭解0-1揹包問題。可點擊該鏈接

想了解三種揹包問題之間的公式以及公式之間有什麼區別。請點擊該鏈接

若想了解沒種揹包問題具體java代碼怎麼寫。可點擊該鏈接

動態規劃的應用場景


  適用動態規劃的問題必須滿足最優化原理、無後效性和重疊性。

  a.最優化原理(最優子結構性質) 最優化原理可這樣闡述:一個最優化策略具有這樣的性質,不論過去狀態和決策如何,對前面的決策所形成的狀態而言,餘下的諸決策必須構成最優策略。簡而言之,一個最優化策略的子策略總是最優的。一個問題滿足最優化原理又稱其具有最優子結構性質。

  b.無後效性 將各階段按照一定的次序排列好之後,對於某個給定的階段狀態,它以前各階段的狀態無法直接影響它未來的決策,而只能通過當前的這個狀態。換句話說,每個狀態都是過去歷史的一個完整總結。這就是無後向性,又稱爲無後效性。

  c.子問題的重疊性 動態規劃將原來具有指數級時間複雜度的搜索算法改進成了具有多項式時間複雜度的算法。其中的關鍵在於解決冗餘,這是動態規劃算法的根本目的。動態規劃實質上是一種以空間換時間的算法,它在實現的過程中,不得不存儲產生過程中的各種狀態,所以它的空間複雜度要大於其它的算法。

1.問題描述


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

爲方便講解和理解,下面講述的例子均先用具體的數字代入,即:eg:number=4,capacity=8

i(物品編號) 1 2 3 4
w(體積) 2 3 4 5
v(價值) 3 4 5 6


2.總體思路


根據動態規劃解題步驟(問題抽象化、建立模型、尋找約束條件、判斷是否滿足最優性原理、找大問題與小問題的遞推關係式、填表、尋找解組成)找出01揹包問題的最優解以及解組成,然後編寫代碼實現。

 
3.動態規劃的原理


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

最優性原理是動態規劃的基礎,最優性原理是指“多階段決策過程的最優決策序列具有這樣的性質:不論初始狀態和初始決策如何,對於前面決策所造成的某一狀態而言,其後各階段的決策序列必須構成最優策略”。

 
4.揹包問題的解決過程


在解決問題之前,爲描述方便,首先定義一些變量:Vi表示第 i 個物品的價值,Wi表示第 i 個物品的體積,定義V(i,j):當前揹包容量 j,前 i 個物品最佳組合 對應的價值,同時揹包問題抽象化(X1,X2,…,Xn,其中 Xi 取0或1,表示第 i 個物品選或不選)。

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

2、尋找約束條件,W1X1+W2X2+…+WnXn<capacity;

3、尋找遞推關係式,面對當前商品有兩種可能性:

  • 包的容量比該商品體積小,裝不下,此時的價值與前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)=max{V(i-1,j),V(i-1,j-w(i))+v(i)}    其中V(i-1,j)表示裝的下,卻不裝

(1)式表明:如果第i個物品的重量大於揹包的容量,則裝人前i個物品得到的最大價值和裝入前i-1個物品得到的最大價是相同的,即物品i不能裝入揹包;

第(2)個式子表明:如果第i個物品的重量小於揹包的容量,則會有一下兩種情況:(a)如果把第i個物品裝入揹包,則揹包物品的價值等於第i-1個物品裝入容量位j-wi 的揹包中的價值加上第i個物品的價值vi; (b)如果第i個物品沒有裝入揹包,則揹包中物品價值就等於把前i-1個物品裝入容量爲j的揹包中所取得的價值。顯然,取二者中價值最大的,作爲把前i個物品裝入容量爲j的揹包中的最優解。

這裏需要解釋一下,爲什麼揹包容量足夠的情況下,還需要 V(i,j)=max{V(i-1,j),V(i-1,j-w(i))+v(i)}?

V(i-1,j-w(i))+v(i) 表示裝了第i個物品後背包中的最大價值,所以當前揹包容量 j 中,必定有w(i)個容量給了第i個揹包。

因此只剩餘j-w(i)個容量用來裝,除了第i件物品的其他所有物品。

V(i-1,j-w(i))是前i-1個物品裝入容量爲j-w(i)的揹包中最大價值。

注意,這裏有一個問題。前i-1個物品裝入容量爲j-w(i)的揹包中最大價值+物品i的價值。可能不如將,前i-1個物品裝入容量爲j的揹包中得到的價值大。也就是說,可能出現 V(i-1,j) > (V(i-1,j-w(i))+v(i))

比如說,將第i個物品放入揹包,可能會導致前面更有價值的物品放不進揹包。因此,還不如不把第i個物品放進去,把空間讓出來,從而能讓前i-1個物品中更有價值的物品能夠放入揹包。從而讓V(i,j)取得最大的值。

所以我們需要 max{V(i-1,j),V(i-1,j-w(i))+v(i)},來作爲把前i個物品裝入容量爲j的揹包中的最優解。

 

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

然後一行一行的填表:

如,i=1,j=1,w(1)=2,v(1)=3,有j<w(1),故V(1,1)=V(1-1,1)=0;
又如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;
如此下去,填到最後一個,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……
所以填完表如下圖:

V(i,j)=max{V(i-1,j),V(i-1,j-w(i))+v(i)}

5、表格填完,最優解即是V(number,capacity)=V(4,8)=10。(也就是說,在有4個可選物品,揹包容量爲8的情況下,能裝入的最大價值爲10)。

 

5.揹包問題最優解回溯(求解 這個最優解由哪些商品組成?)


通過上面的方法可以求出揹包問題的最優解,但還不知道這個最優解由哪些商品組成,故要根據最優解回溯找出解的組成,根據填表的原理可以有如下的尋解方式:

  • V(i,j)=V(i-1,j)時,說明沒有選擇第i 個商品,則回到V(i-1,j);
  • V(i,j)=V(i-1,j-w(i))+v(i)時,說明裝了第i個商品,該商品是最優解組成的一部分,隨後我們得回到裝該商品之前,即回到V(i-1,j-w(i));
  • 一直遍歷到i=0結束爲止,所有解的組成都會找到。

就拿上面的例子來說吧:

  • 最優解爲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);
  • 有V(3,3)=V(2,3)=4,所以第3件商品沒被選擇,回到V(2,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);
  • 有V(1,0)=V(0,0)=0,所以第1件商品沒被選擇。

動態規劃法求解0/1揹包問題:


1)基本思想:

令表示在前個物品中能夠裝入容量爲的揹包中的物品的最大值,則可以得到如下動態函數:


代碼實現
爲了和之前的動態規劃圖可以進行對比,儘管只有4個商品,但是我們創建的數組長度爲5。。

 
import java.util.*;
 
public class DynamicProgramming {
 
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		while (sc.hasNext()) {
 
			/* 1.讀取數據 */
 
			int number = sc.nextInt(); // 物品的數量
 
			// 注意:我們聲明數組的長度爲"n+1",並另score[0]和time[0]等於0。
			// 從而使得 數組的下標,對應於題目的序號。即score[1]對應於第一題的分數,time[1]對應於第一題的時間
			int[] weight = new int[number + 1]; // {0,2,3,4,5} 每個物品對應的重量
			int[] value = new int[number + 1]; // {0,3,4,5,6} 每個物品對應的價值
 
			weight[0] = 0;
			for (int i = 1; i < number + 1; i++) {
				weight[i] = sc.nextInt();
			}
 
			value[0] = 0;
			for (int i = 1; i < number + 1; i++) {
				value[i] = sc.nextInt();
			}
 
			int capacity = sc.nextInt(); // 揹包容量
 
			/* 2.求解01揹包問題 */
 
			int[][] v = new int[number + 1][capacity + 1];// 聲明動態規劃表.其中v[i][j]對應於:當前有i個物品可選,並且當前揹包的容量爲j時,我們能得到的最大價值
 
			// 填動態規劃表。當前有i個物品可選,並且當前揹包的容量爲j。
			for (int i = 0; i < number + 1; i++) {
				for (int j = 0; j < capacity + 1; j++) {
					if (i == 0) {
						v[i][j] = 0; // 邊界情況:若只有0道題目可以選做,那隻能得到0分。所以令V(0,j)=0
					} else if (j == 0) {
						v[i][j] = 0; // 邊界情況:若只有0分鐘的考試時間,那也只能得0分。所以令V(i,0)=0
					} else {
						if (j < weight[i]) {
							v[i][j] = v[i - 1][j];// 包的容量比當前該物品體積小,裝不下,此時的價值與前i-1個的價值是一樣的,即V(i,j)=V(i-1,j);
						} else {
							v[i][j] = Math.max(v[i - 1][j], v[i - 1][j - weight[i]] + value[i]);// 還有足夠的容量可以裝當前該物品,但裝了當前物品也不一定達到當前最優價值,所以在裝與不裝之間選擇最優的一個,即V(i,j)=max{V(i-1,j),V(i-1,j-w(i))+v(i)}。
						}
					}
				}
			}
 
			System.out.println();
			System.out.println("動態規劃表如下:");
			for (int i = 0; i < number + 1; i++) {
				for (int j = 0; j < capacity + 1; j++) {
					System.out.print(v[i][j] + "\t");
				}
				System.out.println();
			}
			System.out.println("揹包內最大的物品價值總和爲:" + v[number][capacity]);// 有number個物品可選,且揹包的容量爲capacity的情況下,能裝入揹包的最大價值
 
			/* 3.價值最大時,包內裝入了哪些物品? */
 
			int[] item = new int[number + 1];// 下標i對應的物品若被選中,設置值爲1
			Arrays.fill(item, 0);// 將數組item的所有元素初始化爲0
 
			// 從最優解,倒推回去找
			int j = capacity;
			for (int i = number; i > 0; i--) {
				if (v[i][j] > v[i - 1][j]) {// 在最優解中,v[i][j]>v[i-1][j]說明選擇了第i個商品
					item[i] = 1;
					j = j - weight[i];
				}
			}
 
			System.out.print("包內物品的編號爲:");
			for (int i = 0; i < number + 1; i++) {
				if (item[i] == 1) {
					System.out.print(i + " ");
				}
			}
			System.out.println("----------------------------");
 
		}
 
	}
 
}


測試結果:
4
2 3 4 5
3 4 5 6
8

動態規劃表如下:
0    0    0    0    0    0    0    0    0    
0    0    3    3    3    3    3    3    3    
0    0    3    4    4    7    7    7    7    
0    0    3    4    5    7    8    9    9    
0    0    3    4    5    7    8    9    10    
揹包內最大的物品價值總和爲:10
包內物品的編號爲:2 4 
----------------------------
5
2 2 3 5 1
5 4 3 5 2
10

動態規劃表如下:
0    0    0    0    0    0    0    0    0    0    0    
0    0    5    5    5    5    5    5    5    5    5    
0    0    5    5    9    9    9    9    9    9    9    
0    0    5    5    9    9    9    12    12    12    12    
0    0    5    5    9    9    9    12    12    14    14    
0    2    5    7    9    11    11    12    14    14    16    
揹包內最大的物品價值總和爲:16
包內物品的編號爲:1 2 4 5 
----------------------------

參考文獻 
https://blog.csdn.net/qq_38410730/article/details/81667885

https://www.cnblogs.com/xym4869/p/8513801.html

https://www.cnblogs.com/variance/p/6909560.html


————————————————
版權聲明:本文爲CSDN博主「春捲同學」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/Rex_WUST/article/details/89336939

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章