01揹包問題

01揹包問題

有N件物品和一個容量爲V的揹包。第i件物品的費用是v[i],價值是w[i]。求解將哪些物品裝入揹包可以使價值總和最大。數組

要麼拿,要麼不拿優化

  • dp解決方法

關鍵就在於找到它的最優子問題,物品爲N個,體積爲V,咱們須要取二維狀態的dpspa

將前i個物品放入體積爲j的揹包中能夠得到的最大價值->dp[i][j]code

只有單件物品就只須要考慮放或者不放,若是放入,體積就須要減去v[i],價值就加上w[i]cdn

好比咱們想要知道前5個物品放入體積爲9的揹包中最大價值是多少,物品id爲0-4blog

也就是求dp[4][9],須要知道dp[3][j],0 < code="">,而後再對物品4進行選取,根據01揹包狀態轉移方程計算dp[4][9]最大價值; <> it

要知道dp[3][j],咱們就要知道dp[2][j],再對物品3進行選取,根據01揹包狀態轉移方程計算dp[3][j]最大價值;io

要知道dp[2][j],咱們就要知道dp[1][j],再對物品3進行選取,根據01揹包狀態轉移方程計算dp[2][j]最大價值;class

要知道dp[1][j],咱們就要知道dp[0][j],再對物品3進行選取,根據01揹包狀態轉移方程計算dp[0][j]最大價值;循環

dp[i][0]初始化爲0(0<=i < p="">

  • 計算流程

public int napzack01_first(int []w,int []v,int V,int N){
        int [][]dp = new int[N][V+1];
        for(int i=0;i<N;i++)
            for(int j=1;j<=V;j++)
                if(j>=v[i])
                    if(i==0) dp[0][j] = w[i];
                    else dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
                else
                    dp[i][j] = dp[i-1][j];
        return dp[N-1][V];
    }複製代碼

第一次優化:二維數組優化爲一維數組

咱們能夠從表格發現,當前行的數據只與上一行的數據有關,因此咱們只要每次循環後確保數組保存了上一次計算的結果

問題以及解決:

咱們從當前循環來看,計算體積爲5的dp值最多隻須要0-4的上一次計算的結果,若是順序從小到大更新的話,咱們計算5時,此時以前的0-4的值都被更新了,不是上一次計算的結果(而是當前計算的結果),然後面的須要上一次前面的數據;因此咱們不能從頭開始,而應該從最後開始更新,從後往前,這樣能夠保證優先更新體積大的值而讓體積小的值保留上一次就算的結果。

//1維01揹包
    public int napzack01_second(int []w,int []v,int V,int N){
        int []dp = new int[V+1];
        for(int i=0;i<N;i++)
            for(int j=V;j>=0;j--)
                if(j>=v[i])
                    if(i==0) dp[j] = w[i];
                    else dp[j] = Math.max(dp[j],dp[j-v[i]]+w[i]);
        return dp[V];
    }複製代碼

所有放滿01揹包:

咱們以前討論了一種是不要求所有放滿的01揹包,咱們再看若是要求所有放滿會有什麼不一樣

若是咱們把物品表換成這樣:

若是咱們想要所有放滿,就拿不到物品4,即便它價值50,可是咱們想要的是要把揹包裝滿,這就涉及到初始化時的問題

咱們須要爲每一次循環放入物品到j的揹包中設置一個可否放滿的標誌,每次判斷這個標誌來檢查當前物品放入可否使得揹包裝滿

咱們只須要在初始化時把一維數組的1-V初始化爲-1,表示當前0個物品放入揹包,不能裝滿這些體積的揹包,把數組dp[0]正常設置爲0,表示體積爲0的揹包能夠被0個物品裝滿;而後咱們在計算數組值時先判斷dp[j-v[i]]是否爲-1,爲-1說明當前物品放入沒法裝滿揹包,則不進行修改數組的值;只有不爲-1才能繼續放入,說明當前物品放入能夠放滿體積爲j的揹包

//所有放滿01揹包
    public int napzack01_fourth(int []w,int []v,int V,int N){
        int []dp = new int[V+1];
        for(int i=0;i<dp.length;i++){
            dp[i] = -1;
        }
        dp[0] = 0;
        for(int i=0;i<N;i++)
            for(int j=V;j>=0;j--)
                if(j>=v[i]&&dp[j-v[i]]!=-1)
                    dp[j] = Math.max(dp[j],dp[j-v[i]]+w[i]);
        return dp[V];
    }複製代碼

徹底揹包

有N件物品(每一個物品都有無限個)和一個容量爲V的揹包。第i件物品的費用是v[i],價值是w[i]。求解將哪些物品(每一個物品均可以裝多個)裝入揹包可以使價值總和最大。

  • 優化前

咱們每種物品最多有選取V/v[i]個,咱們能夠再多一次循環,計算選取0-V/v[i]個的最大價值

時間複雜度:每種物品有V/v[i]個,共須要求解N*V中狀態,時間爲O(NV*Σ(V/v[i]))

空間複雜度:O(N)

狀態轉移方程:

dp[j] = max{dp[j-1],dp[j-k*v[i]]+k*w[i]}

code

//徹底揹包
    //狀態轉移方程 dp[j] = dp[j-k*v[i]]+k*w[i]
    public int napzack_complete(int []w,int []v,int N,int V){
        int dp[] = new int [V+1];
        for(int i=0;i<N;i++)
            for(int j=0;j<=V;j++)
                for(int k=0;k*v[i]<=j;k++)
                    dp[j] = Math.max(dp[j-1],dp[j-k*v[i]]+k*w[i]);
        return dp[V];
    }複製代碼

  • 優化爲01揹包

咱們記得在優化01揹包時,咱們爲了獲取到上一次計算的值,咱們選擇從後往前計算,可是徹底揹包正好相反,這纔是它此昂要的,徹底揹包由於須要累計多個同一物品的值,前一次計算多是1個、2個等等,下一次j變化了之後,計算的多是3個或者更多,因此咱們須要保存實時計算出來的多個同一物品的最大價值,咱們選取從前日後的順序,這樣每次前面計算的咱們均可以在j增大之後累加得到更多個同一物品的最大價值(根據狀態轉移方程可知,咱們計算一個位置的最大價值只須要當前位置的上一次計算的值和當前次循環內更前面的值)

例如:

在咱們計算第2個物品dp[5]的時候,物品2的體積爲2,價值爲5,咱們須要上一次計算也就是第一個物品的dp[5]的值,還須要dp[5-2]=dp[3]的值,dp[3]咱們在本次循環內計算dp[5]以前就已經算過了,dp[3]可能選了一個物品2,也可能沒有選,咱們計算dp[5]就根據這個dp[3]的大小在進行選取,就能夠進行屢次選取。

根本上就是把一類物品轉化爲多個一種物品

咱們不須要體積從0開始計算。而只須要在每一個物品的循環內從當前物品的最小個數1開始,也就是v[i](不須要0個是由於初始化的時候已經把體積爲0的dp值設置爲0了)

參數依然使用

code

//把徹底揹包優化爲01揹包
    public int napzack_comlete01(int []w,int []v,int N,int V){
        int dp[] = new int[V+1];
        for(int i=0;i<N;i++)
            for(int j=v[i];j<=V;j++)
                dp[j] = Math.max(dp[j],dp[j-v[i]]+w[i]);
        return dp[V];
    }複製代碼
相關文章
相關標籤/搜索