动态规划——背包问题附Java代码

前言

更新至01背包、完全背包。。。。

一、01背包

基础版(二维)

题目:

给定N个物品,每个物品有一个重量W和一个价值V。你有一个能装M重量的背包.问怎么装使得所装物品的总价值最大。每个物品只有一个

①首先,创建二维数组bag01,int[n + 1][w + 1]存储更新最优解

int[] bag01=new int[n + 1][w + 1];
int[] values = new int[n + 1];//存储各个物品的价值
int[] costs = new int[n + 1];//存储物品i的重量

bag[i][j]代表有前i个物品,背包容量为j时可装下的最大总价值(这里及以下所指的容量即”背包可装的重量“)
②初始化bag01[0][k]=0,k∈[0,M],即没有物品可装,总价值为0。
bag01[k][0]=0,k∈[0,N],背包容量为0,也装不了东西,所以总价值为0.
因为java数组创建默认值为0,所以可以不必再进行初始化赋值。

③bag01[i][j] :容量为j (如果j>=costs[i],即能装下第i件物品的情况下)时取不取第i件物品。第i在取第i件物品与不取第i件物品中选中最大值
1> 不取第i件物品,即dp[i−1][j];最优解等价于没有第i件物品(反正也不取),容量为j的情况。
2> 取第i件物品(前提是能装下),那么只剩下j - costs[i]的容量来取前i-1件物品,前i-1件物品取得的最优解等价于bag01[i - 1][j - costs[i]],然后加上第i件物品的价值得到总价值:bag01[i - 1][j - costs[i]] + values[i]。

bag01[i][j] = Math.max(bag01[i - 1][j - costs[i]] + values[i], bag01[i - 1][j]);

for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= w; j++) {
        if (j >= costs[i])
            bag01[i][j] = Math.max(bag01[i - 1][j - costs[i]] + values[i], bag01[i - 1][j]);
        else
            bag01[i][j] = bag01[i - 1][j];
    }
}

一维优化版

我们可以看二维递推式:

bag01[i][j]=Math.max(bag01[i-1][j-costs[i]]+values[i],bag01[i-1][j]);

由二维递推式知:bag01[i][j]的更新实际上只与bag01[i-1]有关,01背包求解的过程是一个填二维表的过程,第i行第j列的结果是bag01[i][j],第i行的结果只与第i-1行有关,所以只需要一维的数据即可完成表 的更新。
数组bag[j]:容量为j时可装得物品的最大价值。

  • ①对于j<costs[i],bag01[i][j] = bag01[i - 1][j];即bag[j]不变,所以不用遍历更新。
  • ②对于j>=costs[i].bag[j]更新后 =Max{ bag[j-1]更新前 + bag[j-costs[i]]更新前+value[i] } ,
  • 第二层循环得逆序,才能拿到更新前的bag[j-1]和bag[j-costs[i]]。同时更新的bag[j],j将大于后面更新的,所以在后面更新的背包值在更新时也不会用到更新后的bag[j]。
 // bag[j]=Max{ bag[j-1],bag[j-cost[i]]+values[i] }
    for(int i=1;i<n;i++) {
        for(int j=w;j>=costs[i];j--) {
        	bag[j] = Math.max(bag[j - costs[i]] + values[i], bag[j]);
        }
    }

完整代码

基础版

import java.util.*;
public class Bag01 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int w = sc.nextInt();
        int[] values = new int[n + 1];
        int[] costs = new int[n + 1];
        for (int i = 1; i <= n; i++) {
            values[i] = sc.nextInt();
        }
        for (int i = 1; i <= n; i++) {
            costs[i] = sc.nextInt();
        }
        int[][] bag01 = new int[n + 1][w + 1];
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= w; j++) {
                if (j >= costs[i])
                    bag01[i][j] = Math.max(bag01[i - 1][j - costs[i]] + values[i], bag01[i - 1][j]);
                else
                    bag01[i][j] = bag01[i - 1][j];
            }
        }
        System.out.println(bag01[n][w]);
    }
}

一维优化版:

import java.util.*;
public class Bag01 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int w = sc.nextInt();
        int[] values = new int[n + 1];
        int[] costs = new int[n + 1];
        for (int i = 1; i <= n; i++) {
            costs[i] = sc.nextInt();
            values[i] = sc.nextInt();
        }
        int[] bag = new int[w + 1];
        Arrays.fill(bag, 0);
        for (int i = 1; i <= n; i++)
            for (int j = w; j >=costs[i]; j--)
                bag[j] = Math.max(bag[j - costs[i]] + values[i], bag[j]);
        System.out.println(bag[w]);
    }
}


二、完全背包

题目:

设有n种物品,每种物品有一个重量及一个价值。但每种物品的数量是无限的,同时有一个背包,最大载重量为M,今从n种物品中选取若干件(同一种物品可以多次选取),使其重量的和小于等于M,而价值的和为最大。

总结:与01背包区别只有每种物品的个数是无限
所以

基础版

递推公式

dp[i][j] =max{ dp[i-1][j] , dp[i][j-costs[i-1]]+values[i-1]}

  • 不装入第i种物品,
    即dp[i−1][j],同01背包;
  • 装入(或再次装入)第i种物品,
    此时和01背包不太一样,
    因为每种物品有无限个(但注意书包限重是有限的),
    所以此时不应该转移到dp[i−1][j−w[i]]而应该转移到dp[i][j−w[i]],
    即装入第i种商品后还可以再继续装入第i种商品。

注意:算法一个物品可能不止被取2次,当容量j足够大,而物品i是性价比足够高的,那么会在的dp[i][j] 中被选中,之前也会在dp[i][j-w[i]]中被选中,在dp[i][j-2w[i]]被选中…


for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= w; j++) {
        if (j >= costs[i])
            bag01[i][j] = Math.max(bag01[i][j - costs[i]] + values[i], bag01[i - 1][j]);
        else
            bag01[i][j] = bag01[i - 1][j];
    }
}

优化版

基础版递推公式

dp[i][j] =max{ dp[i-1][j] , dp[i][j-costs[i-1]]+values[i-1]}

我们有优化版递推公式

dp[j] = Math.max(dp[j],dp[j-costs[i]]+values[i]);

注意基础版第i行的数据的产生不只用到了第i-1行,还遇到了本次循环更新后的dp[j-costs[i-1],第二层循环要顺序执行
原因如下:
dp[j] *=max{ dp[j] , dp[j-costs[i-1]]+values[i-1] * },注:dp[j] *是指本轮循环中更新后的dp[j]值,用dp[i]代表未更新前的值
在产生dp[j] * 要用到dp[j-costs[i-1]] *,而不是dp[j-costs[i-1]] ,所以必须之前就先产生dp[j-costs[i-1]] *
又因为j>j-costs[i-1],所以如果产生dp[j-costs[i-1]],按下面的代码不会更新dp[j];所以不会影响dp[j] *的产生

完整代码

基础版

import java.util.*;
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int w = sc.nextInt();
        int n = sc.nextInt();
        int[] values = new int[n + 1];
        int[] costs = new int[n + 1];
        for (int i = 1; i <= n; i++) {
            costs[i] = sc.nextInt();
            values[i] = sc.nextInt();
        }
        int[][] bag01 = new int[n + 1][w + 1];
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= w; j++) {
                if (j >= costs[i])
                    bag01[i][j] = Math.max(bag01[i][j - costs[i]] + values[i], bag01[i - 1][j]);
                else
                    bag01[i][j] = bag01[i - 1][j];
            }
        }
        System.out.println("max="+bag01[n][w]);
    }
}

优化版


import java.util.*;

public class Bag_Complete {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int M = scanner.nextInt();
        int N = scanner.nextInt();
        int[] values = new int[N+1];
        int[] costs = new int[M+1];
        for (int i = 1; i <= N; i++) {
            costs[i] = scanner.nextInt();
            values[i] = scanner.nextInt();
        }
        int[] dp = new int[M+1];
        for(int i = 1; i <=N; i++) {
            for(int j = costs[i]; j <=M;j++){
                dp[j] = Math.max(dp[j],dp[j-costs[i]]+values[i]);
            }
        }
        System.out.println(dp[M]);
    }
}

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值