2018-03-15 13:11:12算法
揹包問題(Knapsack problem)是一種組合優化的NP徹底問題。問題能夠描述爲:給定一組物品,每種物品都有本身的重量和價格,在限定的總重量內,咱們如何選擇,才能使得物品的總價格最高。問題的名稱來源於如何選擇最合適的物品放置於給定揹包中。數組
類似問題常常出如今商業、組合數學,計算複雜性理論、密碼學和應用數學等領域中。函數
1、0/1揹包問題優化
揹包問題是個NPC問題,01揹包能夠經過動態規劃算法在僞多項式時間內給出解。spa
0/1揹包問題的特色是,每種物品僅僅有一件,且須要選擇放或者不放。3d
在0/1揹包問題中,物品i或者被裝入揹包,或者不被裝入揹包,設xi表示物品i裝入揹包的狀況,則當xi=0時,表示物品i沒有被裝入揹包,xi=1時,表示物品i被裝入揹包。根據問題的要求,有以下約束條件和目標函數: blog
因而,問題歸結爲尋找一個知足約束條件式2.1,並使目標函數式2.2達到最大的解向量X=(x1, x2, …, xn)。數學
0/1揹包問題能夠看做是決策一個序列(x1, x2, …, xn),對任一變量xi的決策是決定xi=1仍是xi=0。在對xi-1決策後,已肯定了(x1, …, xi-1),在決策xi時,問題處於下列兩種狀態之一:
(1)揹包容量不足以裝入物品i,則xi=0,揹包不增長價值;
(2)揹包容量能夠裝入物品i,則xi=1,揹包的價值增長了vi。
這兩種狀況下揹包價值的最大者應該是對xi決策後的揹包價值。令V(i, j)表示在前i(1≤i≤n)個物品中可以裝入容量爲j(1≤j≤C)的揹包中的物品的最大值,則能夠獲得以下動態規劃函數:class
式2.3代表:把前面i個物品裝入容量爲0的揹包和把0個物品裝入容量爲j的揹包,獲得的價值均爲0。
式2.4的第一個式子代表:若是第i個物品的重量大於揹包的容量,則裝入前i個物品獲得的最大價值和裝入前i-1個物品獲得的最大價值是相同的,即物品i不能裝入揹包;第二個式子代表:若是第i個物品的重量小於揹包的容量,則會有如下兩種狀況:
(1)若是把第i個物品裝入揹包,則揹包中物品的價值等於把前i-1個物品裝入容量爲j-wi的揹包中的價值加上第i個物品的價值vi;
(2)若是第i個物品沒有裝入揹包,則揹包中物品的價值就等於把前i-1個物品裝入容量爲j的揹包中所取得的價值。顯然,取兩者中價值較大者做爲把前i個物品裝入容量爲j的揹包中的最優解。 變量
舉個例子:
例如,有5個物品,其重量分別是{2, 2, 6, 5, 4},價值分別爲{6, 3, 5, 4, 6},揹包的容量爲10。
根據動態規劃函數,用一個(n+1)×(C+1)的二維表V,V[i][j]表示把前i個物品裝入容量爲j的揹包中得到的最大價值。
第一階段,只裝入前1個物品,肯定在各類狀況下的揹包可以獲得的最大價值;
第二階段,只裝入前2個物品,肯定在各類狀況下的揹包可以獲得的最大價值;
依此類推,直到第n個階段。最後,V(n,C)即是在容量爲C的揹包中裝入n個物品
時取得的最大價值。
如何肯定裝入揹包的具體物品?
從V(n,C)的值向前推,若是V(n,C)>V(n-1,C),代表第n個物品被裝入揹包,前n-1個物品被裝入容量爲C-wn的揹包中;不然,第n個物品沒有被裝入揹包,前n-1個物品被裝入容量爲C的揹包中。依此類推,直到肯定第1個物品是否被裝入揹包中爲止。由此,獲得以下函數:
public class Knapsack { static int knapsack(int[] v, int[] w, int W) { int n = v.length; int[][] V = new int[n + 1][W + 1]; int[] x = new int[n + 1]; for (int i = 0; i < W + 1; i++) { V[0][i] = 0; } for (int i = 0; i < n + 1; i++) { V[i][0] = 0; } for (int i = 1; i <= n; i++) { for (int j = 1; j <= W; j++) { if (j < w[i - 1]) V[i][j] = V[i - 1][j]; else V[i][j] = Math.max(V[i - 1][j - w[i - 1]] + v[i - 1], V[i - 1][j]); } } int j = W; for (int i = n; i >= 1; i--) { if (V[i][j] == V[i - 1][j]) x[i] = 0; else { j -= w[i - 1]; x[i] = 1; } } for (int i = 1; i <= n; i++) { System.out.println(x[i]); } return V[n][W]; } public static void main(String[] args) { System.out.println(knapsack(new int[]{6, 3, 5, 4, 6}, new int[]{2, 2, 6, 5, 4}, 10)); } }
相關的優化處理:
時間複雜度已經沒法進一步進行優化了,可是空間複雜度仍是有優化餘地的,經過遞推式能夠看到,每次下一行的值的產生僅僅依賴於上一行的前面兩個值,所以,咱們能夠將二維數組優化成一維數組進行存儲。
static int polish(int[] v, int[] w, int W){ int n = v.length; int[] m = new int[W + 1]; m[0] = 0; for (int i = 1; i <= n; i++) { for (int j = W; j >= w[i - 1]; j--) { m[j] = Math.max(m[j - w[i - 1]] + v[i - 1], m[j]); } } return m[W]; }
另外,在初始化的時候,若是是題目沒有要求必須得最終裝滿揹包,則直接使用上述代碼便可,若是題目中指出必須裝滿揹包,則在初始化的時候,除了V[0][0] = 0外,其他的0件物品,j個重量,抑或j個重量,0件物品都是不知足裝滿揹包的條件的,應該初始化爲負無窮大。
2、徹底揹包問題
徹底揹包問題一樣給出了n件物品的重量和價值,而且給出了揹包的大小W,可是和0/1揹包不一樣的是,在徹底揹包問題中,每件物品能夠選1,2,3...直到揹包放不下爲止。
徹底揹包是0/1揹包問題的一個擴展,也一樣是一個很是經典的問題。
運用類比的思想,咱們能夠將徹底揹包轉化成0/1揹包,具體的轉化能夠有下面兩種方式:
一、將每件物品當作W/w[i]件,價值不變;
二、將每件物品當作v[i]*2^k,重量爲w[i]*2^k,想法就是利用二進制的角度看問題,任何多種選擇均可以經過這些二進制數相加獲得,這種方法的分解個數顯然要小不少,很是聰明。
若是從遞推式的角度來解決問題,能夠獲得一個很是好的解答:
V[i][j] = max{V[i - 1][j], V[i][j - w[i]] + v[i]}
對於每個V[i][j]均可以當作要麼不選擇第i件,要麼選擇第i件且能夠多選,那麼就能夠很容易的獲得上述的遞推式。
下面使用一維數組進行實現,你會發現除了內層的順序變了,其餘的都沒有改變。
static int polish(int[] v, int[] w, int W){ int n = v.length; int[] m = new int[W + 1]; m[0] = 0; for (int i = 1; i <= n; i++) { for (int j = w[i - 1]; j <= W; j--) { m[j] = Math.max(m[j - w[i - 1]] + v[i - 1], m[j]); } } return m[W]; }