背包问题分类

1.0-1背包问题

有 n 个物体,重量分别为 wi,价值为 vi,在总重量不超过容量 C 的情况下让总价值最高,

但不允许只取走部分物体,要拿走只能整个拿走。

从01背包开始就涉及到了动态规划的知识,动态规划的本质就是递推。

先打表进行观察:
在这里插入图片描述

表中 dp[ i ] [ j ] 表示容量为 j,可以在 0 号物品到 i 号物品中进行任意组合,这种情况下的最大值。

第一行与第一列都不用说,一眼就可以直接得到答案。

剩下的部分怎么进行选择呢?

比如 dp[2] [4] = 6,我们已经知道了 dp[1] [4] = 5,说明在没有考虑 2 号物品的时候最大价值为 5。
现在加入了 2 号物品需要考虑,(3,4)重量为 3,价值为 4。

那么必须要选择 2 号物品才有可能超过之前计算不考虑 2 号物品的最大价值 5。

而选择 2 号物品的最大价值为多少呢?

应该是 dp[2-1] [4-3] + 4 = dp[1] [1] + 4 = 2 + 4 = 6,

dp[1] [1] 为不考虑 2 号物品,容量为 1 的最大价值,这样正好可以装下重量为 3 的 2 号物品且价值最大。

因此我们只要比较加入 2 号物品的最大价值与不加入 2 号物品的最大价值即可,
即 max(dp[1] [1] + 4, dp[1] [4])。

一般情况:dp[i] [j] = max( dp[i-1] [j-w] + v, dp[i-1] [j] )。第i件物品的重量为w,价值为v

所以处理到最后 dp[2] [5] 就是我们要求的答案。
/**
* 0-1背包问题
* @param V 背包容量
* @param N 物品种类
* @param weight 物品重量
* @param value 物品价值
* @return
*/
public static String ZeroOnePack(int V,int N,int[] weight,int[] value){

    //初始化动态规划数组
    int[][] dp = new int[N+1][V+1];
    //为了便于理解,将dp[i][0]和dp[0][j]均置为0,从1开始计算
    for(int i=1;i<N+1;i++){
        for(int j=1;j<V+1;j++){
            //如果第i件物品的重量大于背包容量j,则不装入背包
            //由于weight和value数组下标都是从0开始,故注意第i个物品的重量为weight[i-1],价值为value[i-1]
            if(weight[i-1] > j)
                dp[i][j] = dp[i-1][j];
            else
                dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-weight[i-1]]+value[i-1]);
        }
    }
    //则容量为V的背包能够装入物品的最大值为
    int maxValue = dp[N][V];
    //逆推找出装入背包的所有商品的编号
    int j=V;
    String numStr="";
    for(int i=N;i>0;i--){
        //若果dp[i][j]>dp[i-1][j],这说明第i件物品是放入背包的
        if(dp[i][j]>dp[i-1][j]){
            numStr = i+" "+numStr;
            j=j-weight[i-1];
        }
        if(j==0)
            break;
    }
    return numStr;  
}

0-1背包的优化解法:

/**
     * 0-1背包的优化解法
     * 思路:
     * 只用一个一维数组记录状态,dp[i]表示容量为i的背包所能装入物品的最大价值
     * 用逆序来实现
     */
    public static int ZeroOnePack2(int V,int N,int[] weight,int[] value){
        //动态规划
        int[] dp = new int[V+1];
        for(int i=1;i<N+1;i++){
            //逆序实现
            for(int j=V;j>=weight[i-1];j--){
                dp[j] = Math.max(dp[j-weight[i-1]]+value[i-1],dp[j]);
            }
        }
        return dp[V];       
    }

2.部分背包问题

有 n 个物体,重量分别为 wi,价值为 vi,在总重量不超过容量 C 的情况下让总价值最高,

每个物体可以只取走一部分,若取走部分物体则价值也会等比例减少。

这种背包问题也比较简单,可以定义一个变量 rate 表示各个物体的性价比

性价比最高的物体先拿走,如果可以整个物体都拿走则直接拿走,若不能整个拿走就拿走部分。

import java.util.Arrays;
import java.util.Comparator;
public class GreedyAlgorithm {
    /**
     * 普通背包问题,可以将物品的一部分放入背包,比如黄金喽
     *      简单贪心:将物品的单位价值降序排列
     * @param s     物品个数,与物品重量,价值一一对应
     * @param w     物品重量
     * @param v     物品价值
     * @param p     背包大小
     * @return      返回最大价值
     */
    public static double normalPackage(int s, double[] w, double[] v, double p){
        double[][] vperw = new double[s][2];//[0]单位重量下的价值,[1]编号
        for (int i = 0; i < s; i++) {
            vperw[i][0] = v[i] / w[i];
            vperw[i][1] = i;
        }
        //按照收益比排序
        Arrays.sort(vperw, new Comparator<double[]>() {
            @Override public int compare(double[] o1, double[] o2) {
                return o2[0]-o1[0]>0?1:-1;
            }
        });
        double tmpW = 0;//临时重量
        double result = 0;//价值
        for (int i = 0; i < s; i++) {
            int b = (int) vperw[i][1];//当前物品的id
            double thisW = w[b];
            if (thisW < p-tmpW){//如果可以放下完整的物品
                result += v[b];
                tmpW += w[b];
                System.out.println("物品 " + b +" 放入比例 1 获得收益 " + v[b]);
            }else {
                double canW = p-tmpW;//可以放的重量
                result += vperw[i][0] * canW;
                System.out.println("物品 " + b +" 放入比例 "+ canW/thisW +" 获得收益 " + vperw[i][0] * canW);
                break;
            }
        }
        return result;
    }

    public static void main(String[] args) {
        double[] w = new double[]{18,15,10};
        double[] v = new double[]{25,24,15};
        double result = normalPackage(w.length, w, v, 20);
        System.out.println("获取最大收益为:" + result);

    }
}

3.完全背包问题

内容:有n件物品和容量为m的背包 给出i件物品的重量以及价值 求解让装入背包的物品重量不超过背包容量 且价值最大

特点: 每个物品可以无限选用

有二维和一维两种,但二维的有一定局限 所以只介绍一维方法 

设f[j]表示重量不超过j公斤的最大价值 可得出状态转移方程 :

    f[j]=max{f[j],f[j−w[i]]+v[i]}

区别:0,1背包是从大到小遍历,完全背包是从小到大遍历

代码如下:
// 完全背包:一维法-倒序
	public static int bag3(int W, int[] w, int[] v) {
		int n = w.length - 1;// 第一个值,不算
		int[] f = new int[W + 1];
		for (int i = 1; i <= n; i++)
                    for (int j = W; j >= w[i]; j--)         
                        for (int k = 0; j - k*w[i] >=0; k++) 
			    f[j] = Math.max(f[j], f[j - k*w[i]] +k* v[i]);
 
		return f[W]; // 最优解
	}

优化: 转换成从小到大,因为每个物品可以用无限次,只要不准超过重量,就不在乎之前i(上一层)是否用过。

// 完全背包:一维法-正序
	public static int bag3(int W, int[] w, int[] v) {
		int n = w.length - 1;// 第一个值,不算
		int[] f = new int[W + 1];
		for (int i = 1; i <= n; i++)
			for (int j = w[i]; j <= W; j++)
				f[j] = Math.max(f[j], f[j - w[i]] + v[i]);
		return f[W]; // 最优解
	}

4.多重背包

内容:有n件物品和容量为m的背包 给出i件物品的重量以及价值 还有数量 求解让装入背包的物品重量不超过背包容量 且价值最大
特点:每个物品都有了一定的数量

设f[j]表示重量不超过j公斤的最大价值 可得出状态转移方程 :

f[j]=max{f[j],f[j−k∗w[i]]+k∗v[i]}

代码如下:

// 多重背包:一维法
	public static int bag4(int W, int[] w, int[] v, int[] num) {
		int n = w.length - 1;// 第一个值,不算
		int[] f = new int[W + 1];
		for (int i = 1; i <= n; i++)
			for (int j = W; j >= w[i]; j--)
				for (int k = 0; k <= num[i] && j - k * w[i] >= 0; k++) {
					f[j] = Math.max(f[j], f[j - k * w[i]] + k * v[i]);
				}
 
		return f[W]; // 最优解
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值