01揹包問題

題目:給一個書包,能夠放最重爲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;
    }
}
相關文章
相關標籤/搜索