0-1背包問題的動態規划實現


一,問題描述

給定一個背包,已知背包的最大承重為packageWeight,再給出若干件(numbers件)物品,已經每件物品的重量和對應的價值。

物品的重量存儲在weight[]數組中,物品的價值存儲在value[]數組中。

現在要求:應該挑選哪幾件物品,使得背包裝下最大的價值(注意:裝的物品的重量不能超過背包的承重

本文在最后打印出了裝入了哪幾件物品

 

二,問題分析

這是一個典型的動態規划求解。對於每件物品而言,只有兩種選擇,要么選中它裝入背包;要么不選它。因此,這是一個0-1背包問題,而不是部分背包問題(只選這件物品的一部分)

關於部分背包問題,可參考:部分背包問題的貪心算法正確性證明

對於DP而言,關鍵是列出它的狀態方程,0-1背包問題的狀態方程與“硬幣找零”問題的狀態方程非常相似。關於硬幣找零,可參考:硬幣找零問題的動態規划實現

 

這里再重新分析一個0-1背包問題的狀態方程:

設 dp[i][j] 表示:在背包最大承重為 j 時,可選的物品件數有 i 件 的情況下,背包裝下的物品的最大價值。

dp[i-1][j-weight[i-1]]+value[i-1]  表示:當將第 i 件物品裝入背包時,背包還能承受的重量變成: j-weight[i-1]  (weight[]數組下標0存儲第一件物品的重量)

由於第 i 件物品已經考慮了(將之裝入到背包了),故現在可裝入的物品 只有 i-1 件了。

 

dp[i-1][j]表示:不將第 i 件物品裝入背包。此時,本次選擇對背包的承重沒有影響,故 j 不變。由於第 i 件物品已經考慮了(不把它裝入背包),故現在可裝入的物品只有 i-1 件了。

由於我們要找能裝入背包的最大價值,在上面兩種情形下,哪種選擇的導致的價值最大,就選誰。從而狀態方程如下:--取二者中較大的那個。

dp[i][j]=max{dp[i-1][j-weight[i-1]]+value[i-1], dp[i-1][j]}

 

三,代碼分析:

假設第一行輸入兩個數: 背包的最大承重和物品的件數----packageWeight    numbers

第二行輸入物品的重量,第三行輸入對應的物品的價值,格式如下:

10 5

2 2 6 5 4
6 3 5 4 6

 1         String packInfo = null;
 2         String weights = null;
 3         String values = null;
 4         while(scanner.hasNextLine()){
 5             packInfo = scanner.nextLine();
 6             int packageWeight = Integer.valueOf(packInfo.split(" ")[0]);
 7             int numbers = Integer.valueOf(packInfo.split(" ")[1]);
 8             
 9             weights = scanner.nextLine();
10             values = scanner.nextLine();
11             String[] weis = weights.split(" ");
12             String[] vals = values.split(" ");
13             //weight[]數組是從下標0開始存儲,索引0存儲第一件物品的重量
14             int[] weight = new int[numbers];
15             int[] value = new int[numbers];
16             
17             for(int i = 0; i < numbers; i++)
18             {
19                 weight[i] = Integer.valueOf(weis[i]);
20                 value[i] = Integer.valueOf(vals[i]);
21             }

以上代碼解析輸入的情況。

 

1 int[][] dp = new int[numbers + 1][packageWeight + 1];
2             
3 //init
4 for(int i = 0; i <= numbers; i++)
5     dp[i][0] = 0;
6 for(int i = 0; i <= packageWeight; i++)
7     dp[0][i] = 0;

這段代碼對背包問題進行初始化。dp[i][0]=0 表示:背包最大承重為0,故不能裝物品,故裝入的物品最大價值也就是0了。

dp[0][j]=0 表示:可選的物品種類為0,背包的最大承重為 j 。都沒有物品可選,怎么裝?沒有物品裝啊。故最大價值也為0。

 

 1 //dp[i][j] = max{dp[i-1][j], dp[i-1][j-weight[i-1]]+value[i-1]}
 2 for(int i = 1; i <= numbers; i++)
 3 {
 4     for(int j = 1; j <= packageWeight; j++)
 5     {
 6         if(weight[i-1] > j)// 第i件物品的重量大於背包的承重
 7                 
 8         {
 9             dp[i][j] = dp[i-1][j];
10             continue;
11         }
12         //dp[i][j] = max{dp[i-1][j], dp[i-1][j-weight[i-1]]+value[i-1]}
13         if(dp[i-1][j] < dp[i-1][j-weight[i-1]] + value[i-1])
14             dp[i][j] = dp[i-1][j-weight[i-1]] + value[i-1];
15         else
16             dp[i][j] = dp[i-1][j];
17     }
18 }

這段代碼,本質上就是狀態方程的實現。它以自底向上(從下標1開始求啊...)的方式求解DP問題。

 

既然,找出了能夠裝入的最大價值,那能不能知道裝入了哪些物品???

當然也是可以知道的。

1 //反向找出 選中的物品(哪些物品裝入到背包中了?)
2 int j= packageWeight;  
3 for(int i = numbers;i>0;i--){
4     if(dp[i][j]>dp[i-1][j]){
5         System.out.print(i+"  ");//輸出選中的物品的編號
6         j=j-weight[i-1];
7         if(j<0) break;
8     }

第四行if語句成立時,說明將第 i 件物品裝入到背包中了,結果導致價值增大。第五行打印出裝入了哪幾件物品。

 

四,完整代碼實現

import java.util.Scanner;  
public class Main {  
    
    public static void zeroOnePack(){
        Scanner scanner = new Scanner(System.in);
        String packInfo = null;
        String weights = null;
        String values = null;
        while(scanner.hasNextLine()){
            packInfo = scanner.nextLine();
            int packageWeight = Integer.valueOf(packInfo.split(" ")[0]);
            int numbers = Integer.valueOf(packInfo.split(" ")[1]);
            
            weights = scanner.nextLine();
            values = scanner.nextLine();
            String[] weis = weights.split(" ");
            String[] vals = values.split(" ");
            //weight[]數組是從下標0開始存儲,索引0存儲第一件物品的重量
            int[] weight = new int[numbers];
            int[] value = new int[numbers];
            
            for(int i = 0; i < numbers; i++)
            {
                weight[i] = Integer.valueOf(weis[i]);
                value[i] = Integer.valueOf(vals[i]);
            }
            
            int[][] dp = new int[numbers + 1][packageWeight + 1];
            
            //init
            for(int i = 0; i <= numbers; i++)
                dp[i][0] = 0;
            for(int i = 0; i <= packageWeight; i++)
                dp[0][i] = 0;
        
            //dp[i][j] = max{dp[i-1][j], dp[i-1][j-weight[i-1]]+value[i-1]}
            for(int i = 1; i <= numbers; i++)
            {
                for(int j = 1; j <= packageWeight; j++)
                {
                    if(weight[i-1] > j)// 第i件物品的重量大於背包的承重
                
                    {
                        dp[i][j] = dp[i-1][j];
                        continue;
                    }
                    //dp[i][j] = max{dp[i-1][j], dp[i-1][j-weight[i-1]]+value[i-1]}
                    if(dp[i-1][j] < dp[i-1][j-weight[i-1]] + value[i-1])
                        dp[i][j] = dp[i-1][j-weight[i-1]] + value[i-1];
                    else
                        dp[i][j] = dp[i-1][j];
                }
            }
            System.out.println(dp[numbers][packageWeight]);//輸出背包能夠裝的最大價值
            
            //反向找出 選中的物品(哪些物品裝入到背包中了?)
            int j= packageWeight;  
            for(int i = numbers;i>0;i--){
                if(dp[i][j]>dp[i-1][j]){
                    System.out.print(i+"  ");//輸出選中的物品的編號
                    j=j-weight[i-1];
                    if(j<0) break;
                }  
        }//end while
    }  
        scanner.close();
}
    //hapjin's test case
    //10 5
    //2 2 6 5 4
    //6 3 5 4 6
    public static void main(String[] args) {
        zeroOnePack();
    }
}
View Code

 

五,參考資料

某種 找換硬幣問題的貪心算法的正確性證明

硬幣找零問題的動態規划實現

部分背包問題的貪心算法正確性證明

從 活動選擇問題 看動態規划和貪心算法的區別與聯系

 

原文:http://www.cnblogs.com/hapjin/p/5818418.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM