手撕算法——01揹包問題

題目描述

01揹包問題
有 N 件物品和一個容量是 V 的揹包。每件物品只能使用一次。

第 i 件物品的體積是 vi,價值是 wi。

求解將哪些物品裝入揹包,可使這些物品的總體積不超過揹包容量,且總價值最大。
輸出最大價值。

輸入格式
第一行兩個整數,N,V,用空格隔開,分別表示物品數量和揹包容積。

接下來有 N 行,每行兩個整數 vi,wi,用空格隔開,分別表示第 i 件物品的體積和價值。

輸出格式
輸出一個整數,表示最大價值。

數據範圍

0<N,V≤1000
0<vi,wi≤100

0
輸入樣例

4 5
1 2
2 4
3 4
4 5

輸出樣例:

8

解決思路
01揹包問題可以用動態規劃來求解
按照之前文章提到的動態規劃的解題模板:
(1)確定數組元素含義
int dp[][]=new int[N+1][V+1]
dp[i][j] 表示揹包容積爲j數量爲i時最大的價值。
(2) 確定狀態轉移方程
當計算第i個物品時無非兩種情況,第一種是放該物品,第二種是不放該物品。
第一種情況:dp[i][i]=dp[i-1][j]
第二種情況 dp[i][j]=dp[i-1][j-v[i]]+w[i],這種情況的前提條件是j>=v[i]
因此 dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-v[j]]+w[j])
(3)確定初始值
dp[0][0…j]=0, dp[0…i][0]即不放任何物品,體積爲0時,總價值爲0;
因爲java中new出來的數組初始值爲0,因此這裏不需要任何的操作
(4)根據初始值和狀態轉移方程確定數組元素的值
(5)返回結果 dp[N][V]
(6)考慮狀態壓縮

使用二維數組的代碼如下:

import java.util.Scanner;
public class Main{
    public static void main(String[] args){
     
        Scanner scanner =new Scanner(System.in);
        int N=scanner.nextInt();//N行  物品數量
        int V=scanner.nextInt();//揹包容積
        int v[]=new int[N+1];
        int w[]=new int[N+1];
      
        for(int i=1;i<=N;i++){
            v[i]=scanner.nextInt();
            w[i]=scanner.nextInt();
        }
        
        int dp[][]=new int[N+1][V+1];//狀態數組
        dp[0][0]=0;
        for(int i=1;i<=N;i++){
            for(int j=1;j<=V;j++){
                dp[i][j]=dp[i-1][j];
                if(j-v[i]>=0){
                    dp[i][j]=Math.max(dp[i][j],dp[i-1][j-v[i]]+w[i]);
                }
                
            }
        }
     
        System.out.println(dp[N][V]);
    }
}

壓縮爲一維數組與二維數組不同之處在於遍歷的時候需要逆序,是爲了第i輪中不覆蓋到第i-1輪中的結果。可以參考下文:
對使用倒序的一維數組解決0/1揹包問題的理解
壓縮爲意味數組的代碼如下:

import java.util.*;
public class Main{
    public static void main(String[] args){
     
        Scanner scanner =new Scanner(System.in);
        int N=scanner.nextInt();//N行  物品數量
        int V=scanner.nextInt();//揹包容積
        int v[]=new int[N+1];
        int w[]=new int[N+1];
      
        for(int i=1;i<=N;i++){
            v[i]=scanner.nextInt();
            w[i]=scanner.nextInt();
        }
        
        int dp[]=new int[V+1];//用來表示該體積下可以容納物品的最大總價值

        for(int i=1;i<=N;i++){
            for(int j=V;j>=v[i];j--){
               
                dp[j]=Math.max(dp[j],dp[j-v[i]]+w[i]);
                
                
            }
        }
     
        System.out.println(dp[V]);
    }
}

更進一步,記錄一下中興的在線筆試題計算反應堆的最大能量,該題目是01揹包問題的變形,不再做過多的贅述

public class Test2_01 {

	public static void main(String[] args) {
		int reactorCap = 100; // 反應堆的容量(V)
		int numberOfRadLiquid = 5; // 現有小瓶數量(N)
		int criticalMass = 15; // 反應堆的最大臨界質量(M)
		int volume[] = { 50, 40, 30, 20, 10 };// 體積
		int masses[] = { 1, 2, 3, 9, 5 };// 質量
		int energies[] = { 300, 480, 270, 200, 180 }; // 能量

		int dp[][][]=new int[numberOfRadLiquid+1][criticalMass+1][reactorCap+1];
		
		for(int i=1;i<=numberOfRadLiquid;i++) {
			for(int j=1;j<=criticalMass;j++) {
				for(int k=1;k<=reactorCap;k++) {
					dp[i][j][k]=dp[i-1][j][k];
					if(j>=masses[i-1]&&k>=volume[i-1]) {
						dp[i][j][k]=Math.max(dp[i][j][k], dp[i-1][j-masses[i-1]][k-volume[i-1]]+energies[i-1]);
					}
				}
			}
		}
		
		System.out.print(dp[numberOfRadLiquid][criticalMass][reactorCap]);
	}
}

空間壓縮

public class Test2_02 {
	public static void main(String[] args) {
		int reactorCap = 100; // 反應堆的容量(V)
		int numberOfRadLiquid = 5; // 現有小瓶數量(N)
		int criticalMass = 15; // 反應堆的最大臨界質量(M)
		int volume[] = { 50, 40, 30, 20, 10 };// 體積
		int masses[] = { 1, 2, 3, 9, 5 };// 質量
		int energies[] = { 300, 480, 270, 200, 180 }; // 能量

		int dp[][]=new int[criticalMass+1][reactorCap+1];
		
		for(int i=1;i<=numberOfRadLiquid;i++) {
			for(int j=criticalMass;j>=masses[i-1];j--) {
				for(int k=reactorCap;k>=volume[i-1];k--) {
					dp[j][k]=Math.max(dp[j][k], dp[j-masses[i-1]][k-volume[i-1]]+energies[i-1]);	
				}
			}
		}
		System.out.print(dp[criticalMass][reactorCap]);
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章