算法学习(三)——01背包

算法学习(三)——01背包

前言

挣扎了这么久以后,终于还是把01背包最简单的搞明白了。卡在01背包这里好几天了,看来我可能就不是一个打ACM的料,算了,还是好好学习算法把。

最基础的01背包模型

题目

N NN 件物品和一个容量为 V VV 的背包。第 i ii 个物品的体积和价值分别是 w e i g h t [ i ] weight[i]weight[i]v a l u e [ i ] value[i]value[i] 。求解将哪些物品放进包里可以使价值最大。

特点

这类题目的很明显的特点是,每个物品都只有一个,可以选择要或者不要。

核心

01背包的题目很相似,其解题的核心也都是一样的,就是下面的状态转移方程
tab[i][j]=max(tab[i-1][j-weight[i]]+value[i],tab[i-1][j])
乍一看,这个所谓的状态转移方程属实有点难理解,我也是卡在这里很久很久才看明白,下面我就把我所理解的一一列出来。

状态转移方程讲解

  • t a b [ i ] [ j ] tab[i][j]tab[i][j] 表示前 i ii 件物品恰放入一个容量为 j jj 的背包可以获得的最大价值
  • i ii 表示放第 i ii 个物品
  • j jj 表示背包容量为 j jj
  • w e i g h t [ i ] weight[i]weight[i] 表示第 i ii 个物品的重量
  • v a l u e [ i ] value[i]value[i] 表示第 i ii 个物品的价值
  • t a b [ i − 1 ] [ j − w e i g h t [ i ] ] + v a l u e [ i ] tab[i-1][j-weight[i]] + value[i]tab[i1][jweight[i]]+value[i] 表示在装完前 ( i − 1 ) (i-1)(i1) 个物品后再装入第 ( i ) (i)(i) 个物品后的价值(如果可以装的下)
  • t a b [ i − 1 ] [ j ] tab[i-1][j]tab[i1][j] 表示不装入第 ( i ) (i)(i) 个物品的价值(因为装不下)
  • max()的第一个参数表示选择第 i ii 个物品的情况,第二个参数表示不选择 i ii 个物品的情况
  • 注意 i ii 要从1开始,因为总有一列tab[0][j]表示不装物品的空背包

以上就是我对于01背包的状态转移方程的理解,如果还是不理解的画,可以参考下面的例题:

例题

5个物品,(重量,价值)分别为:(5,12),(4,3),(7,10),(2,3),(6,6)
然后我们可以通过状态转移方程得到下面的表格
Alt text
这里表格的横坐标为 j jj,纵坐标为 i ii
表格的具体分析可以参考上面状态转移方程的理解。

实现

for (int i=1;i<=m;i++){
	for (int j=0;j<=t;j++){
	          if (j<wight[i])//背包容量小于物品质量时
	              tab[i][j]=tab[i-1][j];
	          else
	              tab[i][j]=Math.max(tab[i-1][j-wight[i]]+values[i],tab[i-1][j]);
	}
}

复杂度分析

时间复杂度: O ( N V ) O(NV)O(NV)
空间复杂度: O ( N V ) O(NV)O(NV)

优化

这里的时间复杂度已经没有办法再优化了,可是空间复杂度却还有优化的余地。考虑我们的第二层循环,这里的每一个**d p [ i ] [ j ] dp[i][j]dp[i][j]的取值是都只和第二维比j jj**小的元素有关。那么是否可以通过改变枚举方向的方式,来把第一维用来保存当前位置的一列删掉呢。显然是可以的。考虑这样的代码:

for (int i = 0; i <= V; i++) dp[i] = 0;
for (int i = 1; i <= n; i++) {
    for (int j = V; j >= C[i]; j--) {
        dp[j] = max(dp[j], dp[j-C[i]]+W[i]);
    }
}

实战

上面说了这么多,还没有真正实战一波,下面我找了一个学校的OJ上的题来小试牛刀一下。

采药【(0-1)背包问题】

Description

辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”
如果你是辰辰,你能完成这个任务吗?

Input

输入的第一行有两个整数T(1 <= T <= 1000)和M(1 <= M <= 100),用一个空格隔开,T代表总共能够用来采药的时间,M代表山洞里的草药的数目。接下来的M行每行包括两个在1到100之间(包括1和100)的整数,分别表示采摘某株草药的时间和这株草药的价值。

Output

输出包括一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。

Sample Input

70 3
71 100
69 1
1 2

Sample Output

3

AC代码

import java.util.Scanner;
/**
 * @program: oj
 * @description: https://www.oj.swust.edu.cn/problem/show/1046
 * @author: Pang
 * @create: 2018-10-25 15:36
 **/
public class Main{
    static Scanner scanner=new Scanner(System.in);
    static final int MAX_TIME=1005;//最大采药时间
    static final int MAX_CONT=105;//最大药品数量
    static int times[]=new int[MAX_TIME];//保存每颗药品需要的时间
    static int values[]=new int[MAX_CONT];//保存每颗药品的价值
    static int tab[][]=new int[MAX_CONT][MAX_TIME];//模拟背包
    public static void main(String args[]){
        int t=scanner.nextInt();//总共的采药时间
        int m=scanner.nextInt();//草药数目
        input(m);//输入草药的采集时间和价值
        for (int i=0;i<=m;i++){
            tab[0][i]=0;//表示采前0个药草的时候的最大价值为0
        }
        for (int i=1;i<=m;i++){
            for (int j=0;j<=t;j++){
                if (j<times[i])
                    tab[i][j]=tab[i-1][j];
                else
                    tab[i][j]=Math.max(tab[i-1][j-times[i]]+values[i],tab[i-1][j]);
            }
        }
        System.out.println(tab[m][t]);
    }
    /**
    * @Description: 输入药品的采集时间和价值
    * @Param:  草药数目
    * @return:  无
    * @Author: Pang
    * @Date: 2018/10/25
    */
    public static void input(int m){
        for (int i=1;i<=m;i++){
            times[i]=scanner.nextInt();
            values[i]=scanner.nextInt();
        }
    }
}

参考

基础动态规划

最通俗易懂的01背包问题讲解

01背包问题吐血详解


版权声明:本文为qq_40894516原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。