如何運送最多的貨物(0-1揹包)

題目:某快遞每天能收到成千上萬的物流單,每個物流單的重量不一。 一位貨車司機開着貨車(限載5噸,含5噸,不考慮限高),想要一次性拿走儘可能重的貨物

以下是貨物清單:
貨物編號 貨物重量(單位:kg)
1. ————-509
2. ————-838
3. ————-924
4. ————-650
5. ————-604
6. ————-793
7. ————-564
8. ————-651
9. ————-697
10. ————649
11. ————747
12. ————787
13. ————701
14. ————605
15. ————644

輸出運送最接近5000kg時的貨物單號。

01揹包

話說有一哥們去森林裏玩發現了一堆寶石,他數了數,一共有n個。 但他身上能裝寶石的就只有一個揹包,揹包的容量爲C。這哥們把n個寶石排成一排並編上號: 0,1,2,…,n-1。第i個寶石對應的體積和價值分別爲V[i]和W[i] 。排好後這哥們開始思考: 揹包總共也就只能裝下體積爲C的東西,那我要裝下哪些寶石才能讓我獲得最大的利益呢?

OK,如果是你,你會怎麼做?你斬釘截鐵的說:動態規劃啊!恭喜你,答對了。 那麼讓我們來看看,動態規劃中最最最重要的兩個概念: 狀態和狀態轉移方程在這個問題中分別是什麼。

我們要怎樣去定義狀態呢?這個狀態總不能是憑空想象或是從天上掉下來的吧。 爲了方便說明,讓我們先實例化上面的問題。一般遇到n,你就果斷地給n賦予一個很小的數, 比如n=3。然後設揹包容量C=10,三個寶石的體積爲5,4,3,對應的價值爲20,10,12。 對於這個例子,我想智商大於0的人都知道正解應該是把體積爲5和3的寶石裝到揹包裏, 此時對應的價值是20+12=32。接下來,我們把第三個寶石拿走, 同時揹包容量減去第三個寶石的體積(因爲它是裝入揹包的寶石之一), 於是問題的各參數變爲:n=2,C=7,體積{5,4},價值{20,10}。好了, 現在這個問題的解是什麼?我想智商等於0的也解得出了:把體積爲5的寶石放入揹包 (然後剩下體積2,裝不下第二個寶石,只能眼睜睜看着它溜走),此時價值爲20。 這樣一來,我們發現,n=3時,放入揹包的是0號和2號寶石;當n=2時, 我們放入的是0號寶石。這並不是一個偶然,沒錯, 這就是傳說中的“全局最優解包含局部最優解”(n=2是n=3情況的一個局部子問題)。 繞了那麼大的圈子,你可能要問,這都哪跟哪啊?說好的狀態呢?說好的狀態轉移方程呢? 別急,它們已經呼之欲出了。

我們再把上面的例子理一下。當n=2時,我們要求的是前2個寶石, 裝到體積爲7的揹包裏能達到的最大價值;當n=3時,我們要求的是前3個寶石, 裝到體積爲10的揹包裏能達到的最大價值。有沒有發現它們其實是一個句式!OK, 讓我們形式化地表示一下它們, 定義d(i,j)爲前i個寶石裝到剩餘體積爲j的揹包裏能達到的最大價值。 那麼上面兩句話即爲:d(2, 7)和d(3, 10)。這樣看着真是爽多了, 而這兩個看着很爽的符號就是我們要找的狀態了。 即狀態d(i,j)表示前i個寶石裝到剩餘體積爲j的揹包裏能達到的最大價值。 上面那麼多的文字,用一句話概括就是:根據子問題定義狀態!你找到子問題, 狀態也就浮出水面了。而我們最終要求解的最大價值即爲d(n, C):前n個寶石 (0,1,2…,n-1)裝入剩餘容量爲C的揹包中的最大價值。狀態好不容易找到了, 狀態轉移方程呢?顧名思義,狀態轉移方程就是描述狀態是怎麼轉移的方程(好廢話!)。 那麼回到例子,d(2, 7)和d(3, 10)是怎麼轉移的?來,我們來說說2號寶石 (記住寶石編號是從0開始的)。從d(2, 7)到d(3, 10)就隔了這個2號寶石。 它有兩種情況,裝或者不裝入揹包。如果裝入,在面對前2個寶石時, 揹包就只剩下體積7來裝它們,而相應的要加上2號寶石的價值12, d(3, 10)=d(2, 10-3)+12=d(2, 7)+12;如果不裝入,體積仍爲10,價值自然不變了, d(3, 10)=d(2, 10)。記住,d(3, 10)表示的是前3個寶石裝入到剩餘體積爲10 的揹包裏能達到的最大價值,既然是最大價值,就有d(3, 10)=max{ d(2, 10), d(2, 7)+12 }。好了,這條方程描述了狀態d(i, j)的一些關係, 沒錯,它就是狀態轉移方程了。把它形式化一下:d(i, j)=max{ d(i-1, j), d(i-1,j-V[i-1]) + W[i-1] }。注意討論前i個寶石裝入揹包的時候, 其實是在考查第i-1個寶石裝不裝入揹包(因爲寶石是從0開始編號的)。至此, 狀態和狀態轉移方程都已經有了。接下來,直接上代碼。

for(int i=0; i<=n; ++i){
    for(int j=0; j<=C; ++j){
        d[i][j] = i==0 ? 0 : d[i-1][j];
        if(i>0 && j>=V[i-1])  d[i][j] >?= d[i-1][j-V[i-1]]+W[i-1];
    }
 }

i=0時,d(i, j)爲什麼爲0呢?因爲前0個寶石裝入揹包就是沒東西裝入,所以最大價值爲0。 if語句裏,j>=V[i-1]說明只有當揹包剩餘體積j大於等於i-1號寶石的體積時, 我才考慮把它裝進來的情況,不然d[i][j]就直接等於d[i-1][j]。i>0不用說了吧, 前0個寶石裝入揹包的情況是邊界,直接等於0,只有i>0纔有必要討論, 我是裝呢還是不裝呢。(完)

自己的實現(java):

public class Knapsack01 {
    public int maxValue(int N,int C,ArrayList<Goods> goods){
        int[][] d=new int[N+1][C+1];//狀態d(i,j)表示前i個寶石裝到剩餘體積爲j的揹包裏能達到的最大價值

        //兩種狀態 1)不選擇goods[i];2)選擇goods[i],則d[i][j]=max{goods[i].value+d[i-1][j-goods[i].weight],d[i-1][j]}
        int i=1,j=1;
        for(i=1;i<=N;i++){
            for(j=1;j<=C;j++){
                if(goods.get(i).weight<=j){
                    d[i][j]=Math.max(goods.get(i).value+d[i-1][j-goods.get(i).weight],d[i-1][j]);
                }
                else 
                    d[i][j]=d[i-1][j];

            }
        }

        //打印選擇的Item
        int rest_space=C;
        for(int m=N;m>=1;m--){
            if(rest_space>=goods.get(m).weight){
                if(d[m][rest_space]-d[m-1][rest_space-goods.get(m).weight]==goods.get(m).value){
                    System.out.print(m+"-");
                    rest_space=rest_space-goods.get(m).weight;
                }
            }
        }

        return d[N][C];
    }

    public ArrayList<Goods> initial(int a[]){
        ArrayList<Goods> goods=new ArrayList<>();
        goods.add(new Goods(0,0));
        for(int i=0;i<a.length;i++)
            goods.add(new Goods(a[i], a[i]));
        return goods;
    }
    public static void main(String[] args) {
        Knapsack01 ks=new Knapsack01();
        int N=15,C=5000;
        int a[]={509,838,924,650,604,793,564,651,697,649,747,787,701,605,644};
        ArrayList<Goods> goods=ks.initial(a);
        int maxValue=ks.maxValue(N, C, goods);
        System.out.println(maxValue);

    }

    private static class Goods{
        private int value;
        private int weight;
        public Goods(int value,int weight) {
            this.value=value;
            this.weight=weight;
        }
    }
}

參考資料:http://www.hawstein.com/posts/dp-knapsack.html

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