動態規劃之海盜船運寶藏問題

閒來無事,想到了自己對解動態規劃問題掌握的還不大熟練,所以做個題練習下。

1,問題描述:

有一輛小船,能夠承載的最高重量爲c,當船承載的重量超過c時,船會沉沒。  現在有n個物品,物品i的重量爲w(i),價值爲v(i),應該如何選擇裝船的物品,保證船不沉,使得裝上船的物品總價值最大?

2,問題分析:

這是一道0-1動態規劃問題。對於動態規劃問題,最重要的是找到遞歸方程。要找到遞歸方程f(x,y),就要知道自變量x,y,因變量f。約束條件c一定是一個自變量,那麼還有一個自變量呢,是w還是v呢,我們可以看見,w和v可以看成是關於n的函數,所以另一個自變量就是n。那麼f(c,n)表示的含義爲:在容量限制爲c下,有n個商品情況下,能夠取得的商品最大價值。

這裏要注意的一點是:我們在程序中,f(c, n)的含義爲,在重量限制爲c,到選取第n個物品時,可以達到的最大價值。這是因爲我們一般會從一個商品開始,而到了第n個商品,也代表會有n種不同的商品。

計算f(c,n)即取第n個物品求最大價值的時候,一定是先過了第n-1個物品來的。從n-1到n有兩條路徑

1,不取第n-1個物品而來。f(c, n-1)

2,取第n-1個物品而來。 f(c-w[n], n-1) + v[n]

遞歸方程爲:

f(c, n) = max( f(c-w[n], n-1) + v[n],  f(c, n-1))

其中:w[n]表示第n個物品的重量,v[n]表示第n個物品的價值。

3,喜聞樂見填表格

一般來說,動態規劃都會填寫最簡單的表格數據,而這些數據,將會用於填充剩下的空白表格。

4,實驗代碼:

public class Test {
    private int c;
    private int n;
    private int[] w;
    private int[] v;

    private int[][] f;// f(x,y)表示在總重限制爲x時,選取第y個物品,可以達到的最大價值。這裏的選取條件是加入第y個物品,容量不超限制,且取得的價值最大。
    // 如果拿了第i個物品,那麼相應的價值f(xxx, i)會比f(yyy, i-1)大,如果不變,說明不取第i個物品(這裏假設的前提是物品的價值爲正整數)
    // 這樣就可以求出到底拿了哪些物品,以及價值的最大值。(最大值一定處於f矩陣的最右下角)

    private Test(int c, int[] w, int[] v) {
        this.c = c;
        this.n = w.length;
        this.w = new int[n + 1];
        this.w[0] = 0;
        System.arraycopy(w, 0, this.w, 1, n);// 使用第1爲做爲數組的開始
        this.v = new int[n + 1];
        this.v[0] = 0;
        System.arraycopy(v, 0, this.v, 1, n);// 使用第1爲做爲數組的開始
        this.f = new int[c + 1][n + 1]; //這裏也可以是反的
        for (int[] ints : this.f) {
            for (int anInt : ints) {
                anInt = Integer.MIN_VALUE;// 全部初始化爲最小值
            }
        }

        for (int i = 0; i < this.n + 1; i++) {
            this.f[0][i] = 0; //容量限制爲0的情況下,最大價值總爲0
        }
    }

    public void result() {
        for (int cc = 1; cc < c + 1; cc++) {//計算第nn件物品在每個容量限制下能取得的最大價值
            for (int nn = 1; nn < n + 1; nn++) {
                if (cc >= w[nn]) {// 當前容量cc是否可以存放第nn個物品
                    f[cc][nn] = Math.max(f[cc - w[nn]][nn - 1] + v[nn], f[cc][nn - 1]);// 切分,這裏是反着分析的,也就是到了計算取第nn個物品求最大價值的時候,
                    // 則一定是先選過了第nn-1個物品來的。從nn-1到nn有兩條路徑
                    // 1,不取第nn-1個物品而來。
                    // 2,取第nn-1個物品而來。
                    // 遞歸方程:
                    // f(cc, nn) = Math.max(取第nn-1個物品而來, 不取第nn-1個物品而來) ==> f(cc, nn) = Math.max(f(cc-w[nn], nn-1) + v[nn], f(cc, nn-1))
                } else {
                    f[cc][nn] = f[cc][nn - 1];// 如果第nn個物品要裝不下了。則是從不取第nn-1個物品而來的
                }
            }
        }

        System.out.println("重量:" + Arrays.toString(w));
        System.out.println("價值:" + Arrays.toString(v));

        for (int i = 0; i < this.f.length; i++) {
            System.out.printf("容量限制爲:%s的最大價值\t", i);
            for (int anInt : this.f[i]) {
                System.out.print(anInt + "\t");
            }
            System.out.println();
        }

        System.out.println("可以獲利最大值爲:" + this.f[c][n]);

        System.out.print("選取的物品編號爲:");
        int cap = this.c;
        for (int i = n; i >= 1; i--) {
            if (f[cap][i] != f[cap][i - 1]) {// 這裏找的是由於i物品的加入,導致的價值變化,
                cap -= w[i];//如果有變化,則表示選取了i物品,否則就是沒有取i物品
                System.out.print(i + "\t");
            }
        }
    }

    public static void main(String[] args) {
        new Test(20, new int[]{20, 10, 5, 15}, new int[]{4, 3, 2, 1}).result();
    }

}

5,輸出:

完成的表格爲:

人工檢驗沒問題。

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