題目:給一個書包,能夠放最重爲10的物體,給不少物體,有重量,對應的有價值,問在書包能夠容納的狀況下,可以得到的最大價值。數組
這是一個01揹包問題,能夠對於一個物體有兩種狀態,放到書包裏和不放到書包裏。學習
如: 物體編號: 1 2 3 4 5 6優化
物體重量: 2 3 1 4 6 5spa
物體價值: 5 6 5 1 19 7code
咱們從第一個物體開始考慮,一直到最後一個,每個有兩種狀態放在書包裏或者放棄掉不放在書包裏。blog
假設如今咱們考慮第n個物體,剩餘的容量爲c,那麼用f(n,c)表示當前能夠得到的最大價值,那麼咱們考慮一下第n個物體,是否是當前最大價值只能由兩個子問題得來:一、取了第n個物體,考慮第n-1個物體,容量c減去第n個物體的重量,同時加上第n個物體的價值;2沒有取第n個物體,那麼就簡單了,咱們只須要考慮第n-1個物體,容量仍是c,價值也不用加(沒有取嘛)。那麼,對於第n(n爲任意數)個物體是否是均可以由之前的結果(第n-1個時的結果和當前這個物體的價值來得到)。做爲數學表達式可寫爲:數學
f(n,c) = max{ f(n-1, c) , f(n-1, c - w) + v} ; w表明第n個物體的重量,v表明第n個物體的價值class
咱們不妨先用一個矩陣來推導一下(先不考慮程序的實現),咱們要使用一個(n + 1) * (c + 1)的矩陣,爲何使用這麼大小的呢?能夠這樣考慮,f(n,c)中,須要n*c,這個沒問題吧,由於若是不知足這個沒有辦法把f(n,c)所有表示出來。爲何要加1呢,由於物體和容量這裏都從1開始,咱們還有0的狀況呢,也就是初始化的狀況,最開始咱們是都爲0的。技巧
好,那麼咱們看一下過程:程序
這是初始化,想一下,若是物體個數爲0,或者揹包的容量爲0,那麼獲得的價值只能是0.
下面咱們分析第一行
(1,1)位置,表明有一個容量有一個物體時的最大價值,能夠取能夠不取第一個。
一、不取。那麼能獲得的價值就是f(0,1)了,對應就是(0,1)位置的價值,爲0
2取。第一個物體的重量爲2,二當前的容量爲1,因此放不下,也爲0.
所以(1,1)位置只能是0.
(1,2)位置,表明有2個容量有一個物體時的最大價值,能夠取能夠不取第一個。
一、不取。那麼能獲得的價值就是f(0,2)了,對應就是(0,2)位置的價值,爲0
2取。第一個物體的重量爲2,二當前的容量爲2,能夠放下,把這個物體放進去。放進去以後,再看上一個物體,上一個物體爲0,容量2-2=0以後,所以爲上一個物體的得到的最大價值爲f(0,0).所以,當前的最大價值爲f(0,0)+5.
比較這兩種狀況,取二者的最大值,所以獲得5.(1,2)位置爲5.
後面的狀況如以上分析,獲得:
看一下整個過程,都是有本身正上面的和上面左邊某一個位置的數值獲得,比較正上方和左上方(左邊移動幾個位置位置,上面第一個物體時,左邊第二個,後面第二個,第三個。。。物體並不必定,與當前物體的重量有關,當前物體重量是多少,那麼就是左邊第幾個,請從圖上考慮一下爲何),獲取大的那一個。爲何能從圖上獲得呢?考慮一下公式:f(n,c) = max{ f(n-1, c) , f(n-1, c - w) + v}。相信能想清楚吧,這裏沒有任何難度了。
第二個物體,第三個物體的過程與第一行同樣。下面我給出整個表格計算完的結果:
爲了更清楚上面的過程,咱們再來分析一個某個位置如何得來,如(5,6)
一、不取。那麼能獲得的價值就是f(4,6)了,對應就是(4,6)位置的價值,爲16
2取。第5個物體的重量爲6,二當前的容量爲6,能夠放下,把這個物體放進去。放進去以後,再看上一個物體,上一個物體爲4,容量6-6=0以後,所以爲上一個物體的得到的最大價值爲f(4,0).所以,當前的最大價值爲f(4,0)+19.
比較這兩種狀況,取二者的最大值,所以獲得19.(5,6)位置爲19.
至此,咱們的分析過程就完成了,如今想一下,若是咱們聲明一個二維數組,是否是就能夠模仿上面的過程把揹包問題解決了呢。
代碼就不貼出來了,由於上面的二維數組能夠優化,咱們最後給一個優化了的代碼。
空間複雜度的優化
上面的二維數組是整個計算過程的關鍵。可是,仔細想一下,當咱們計算第5個物體時,只涉及到了數組中這一行和這一行的上一行。這一行是上一行的數據計算獲得的,仍是公式,兩種狀況的一種。總之,無論這個物體有沒有取,都要去看上一個物體,所以咱們能夠把真個二維數組優化成只有兩行的,例如這兩行:
取第4個物體,都是根據第三個物體計算出來的。
到這裏已經取得了極大的空間優化。其實,接着認真考慮一下,咱們每次都是把上一行的計算,填寫到下一行。咱們能不能用一行呢?
固然能夠,可是這裏涉及到一個小技巧。
若是仍是從小到大更新(從作到右),咱們右邊的更新都要依賴左邊了,若是從左到右左邊的變化了,右邊的就沒有辦法計算了。一次能夠從右邊向左邊更新,左邊的更晚更新,不會影響到右邊的。這樣不就能夠用一行解決了嗎。舉例子來看:
咱們要更新(4,10)位置,第4個物體重量爲4,若是取的話,那麼當前的價值就是f(4-1, 10-4)+1=f(3,6)+1=16+1=17;若是咱們在原位置上更新,左邊的也不會受到影響。這樣就用一行就能夠了。
咱們再來看一下向左更新終止條件,其實只要到c-w就能夠了(w爲當前物體的容量,c爲一共給的容量)。爲何呢?由於小於這個數的時候,容量放不下當前物體,他取這個物體獲得的價值爲0(其實取不到,由於放不下),不取這個物體(在上面二維數組中,就是正上方的值)的價值就是當前數組這個位置的數,沒有必要更新。所以終止條件就是到c-w就能夠了。
其實這是一個動態規劃的問題,只不過是最簡單的。想學習動態規劃的能夠去學習一下。
下面貼上優化了空間複雜度的代碼:
public class pack { public static void main(String args[]) { Scanner scanner = new Scanner(System.in); int maxValue = scanner.nextInt();//最大容量 int num = scanner.nextInt();//共有的包裹數目 int[] weight = new int[num];//包裹的重量 int[] value = new int[num];//包裹的價值 for (int i = 0; i < num; i++) { weight[i] = scanner.nextInt(); } for (int i = 0; i < num; i++) { value[i] = scanner.nextInt(); } int[] a = new int[maxValue + 1]; for (int i = 0; i < num; i++) {//num爲物體的個數,至關於二維數組從上到下 ZeroOnePack(weight[i], value[i], a, maxValue); } System.out.println(a[maxValue]); } public static void ZeroOnePack(int weight, int value, int[] a, int maxValue) { for (int i = maxValue; i >= weight; i--) {//在原數組上更新新值,從後向前,終止條件也沒必要到0 a[i] = max(a[i], a[i - weight] + value);//a[i]意思是沒有取當前的物體 } } public static int max(int a, int b) { return a > b ? a : b; } }