动态规划之背包问题

动态规划(Dynamic Programming,DP),在平时的应用非常广泛,几乎在所有的比赛和面试当中都可以看到DP的身影,所以我来说一下什么是DP动态规划,然后顺便讲解一个非常经典的DP问题–背包问题。

DP基本思想

是一种在数学、计算机科学和经济学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。 动态规划算法是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或者说分治)的方式去解决。

核心是将一个问题分解成为若干个子问题

什么是动态规划?

  • 动态规划(Dynamic Programming)对于子问题重叠的情况特别有效,因为它将子问题的解保存在表格中,当需要某个子问题的解时,直接取值即可,从而避免重复计算!
  • 动态规划是一种灵活的方法,不存在一种万能的动态规划算法可以解决各类最优化问题(每种算法都有它的缺陷)。所以除了要对基本概念和方法正确理解外,必须具体问题具体分析处理,用灵活的方法建立数学模型,用创造性的技巧去求解。

适用问题

  • 最优化原理:最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构
  • 无后项性:每个状态都是过去历史的一个完整总结(某状态以后的过程不会影响以前的状态)
  • 子问题重叠问题:我们必须保证我们分割成的子问题也能按照相同的方法分割成更小的自问题, 并这些自问题的最终分割情况是可以解决的。

经典案例

背包问题

题目:有n个物品,它们有各自的体积和价值,现有给定容量的背包,如何让背包里装入的物品具有最大的价值总和?
通过题意可以得知这个问题我们通过整体求解无法得出结果 ,在这里我们用递推,分治来解决此问题,也就是将大问题分解成小的问题,然后求解小问题就会方便很多,此问题中,我们可以将问题分成偷或不偷两个问题,还有一个终止条件,那就是当我们的商品重量大于背包内的剩余容量的时候就会先只偷他之前的商品,
在这里插入图片描述
如上图所示的就是这个问题的一个求解公式,左边的是公式,右边的是C语言的实现代码。
在这里插入图片描述
我们的代码就是根据上面的这个矩阵来实现的,例如在有五个商品,背包的容量是二十公斤的时候,我们可以根据图表查看得到的最大价值就是26,图中的每个格子就是当前容量和可以容纳的商品的最大价值,通过上图我们可以得知我们需要一个二维数组来存放我们商品所有的价值,在最后取出来的就是最大的价值,二维数组的范围是N个商品,和M的容量,起始点都是1,我们现需要把这个二维数组置0,在一开始我们定义二维数组的时候我们多定一个一位,即6,21。我们这样很方便来计算,我们下面来展示一下Java源码。
Java代码实现

package Test;

//动态规划之背包问题
public class Main {

    static int W = 21;// 只能存放20公斤
    static int V = 6;// 五个商品
    static int[][] array = new int[V][W];
    static int[] w = { 0, 2, 3, 4, 5, 9 };// 每个商品的重量
    static int[] v = { 0, 3, 4, 5, 8, 10 };// 每个商品的价值

    public static void backpack() {
        int i, j;// i是商品 j是背包容量
        for (i = 1; i < V; i++) {
            for (j = 1; j < W; j++) {
                // 当前物品的重量比我背包容纳的重量还大就先只偷i-1个商品
                if (w[i] > j) {// 当此商品太重的时候只能偷前面的i-1件 j是当前重量
                    array[i][j] = array[i - 1][j];
                } else {// 可以偷了 偷或者是不偷 然后取最大值
                    int value1 = array[i - 1][j - w[i]] + v[i];
                    int value2 = array[i - 1][j];
                    if (value1 > value2) {
                        array[i][j] = value1;
                    } else {
                        array[i][j] = value2;
                    }
                }
            }
        }
    }

    public static void main(String[] args) {

        for (int i = 0; i < V; i++) {
            for (int j = 0; j < W; j++) {
                array[i][j] = 0;
            }
        }
        backpack();
        System.out.println(array[5][20]);
    }
}

结果:
在这里插入图片描述
这个结果和在上面那个表中的值是一样的,所以我们求解成功,26即为五个商品,背包容量为二十公斤的所容纳的最大价值!
在这里插入图片描述

求解思路详解

首先我们先定义一个二维数组用来存放商品的价值,然后定义两个一维数组,一个用来存放每个商品的重量,另一个用来存放每个商品的价值。这两个一维数组我们的第0位都是0,这样做是方便计算,如果不加0的话我们在循环的时候因为我们是从1开始的,所以我们必须加0,否则取到的就会是第二个商品的信息。再定义两个常量,背包的容量,存放商品的个数,二者都加一,这样也是因为我们的for循环是从1开始的。
做好了这些准备之后我们首先给二维数组赋值,让二维数组里面所有的元素的值都是0,之后我们再调用一个我们事先定义好的backpack函数。
在backpack函数里面,我们没有传任何的参数,我们直接定义两个变量i和j,因为是双重for循环,所以我们必须有两个变量,来控制循环,i用来表示商品的个数,从1到V(6),j用来表示背包的容量,从1到W(21)。然后我们开始循环,当我们的商品是第一个的时候,我们将该商品的重量和背包的容量进行对比,发现背包放不开,然后我们二维数组存放的就是0价值。之后还是第一个商品,只不过是我们背包的剩余容量变大了,当我们的背包容量达到2的时候我们发现我可以存放这个商品了,所以我们来到了else语句来进行选择偷或者是不偷,不管是偷或者是不偷,我们最后得到的都是最大的价值。就像是我们现在商品1,然后他的重量是2,价值是3.通过偷我们可以得到他的价值是背包容量减去此商品的重量加上此商品的价值,然后不偷的话结果是0,所以我们二维数组存放的就是这个商品的价值3,所以由上图可以看到这个横纵坐标对应的格子里就是3。之后我背包的容量不断增加,但是商品依旧是哪一个商品,所以此行的价值都是3。当我们循环到第二个商品的时候发现第二个商品的重量是3,价值是4,然后我们的背包容量依旧是从1增长到20,如第一次循环一样,最后都会得到一个价值,并且将这个价值的结果放在这个二维数组当中,显然这些不是最大的价值,这个只能说是局部某一个商品和某一部分背包容量的最大价值,但并不能代表本题的五个商品和二十公斤背包容量的最大价值,这个的最大价值肯定是二维数组上横纵坐标为5和20的那个点,那个点存放的就是此问题的最大价值。
我们通过递推可以得出最后的结论,那就是当我们的背包满了的时候除了商品2不放,别的都放进去,这样的话就是最大的价值,要问为什么会得到最大的价值呢?这也是因为我们在偷或不偷的时候取的永远是最大的那个值,无论你偷不偷这个商品,我们的价值永远是最大的。
以上就是该问题的基本解决思路了,动态规划可以将背包问题分解成偷或者是不偷的小问题,并且取得是价值最大的,不论你偷不偷。依次递推,到最后我们的背包容量满了的时候,此时的价值就是最大的!!
DP可以用来解决很多问题,像跳格子,爬楼梯等经典案例,运用了DP之后很复杂的问题也就会变得简单起来,动态规划,然后分而治之,递推,最后解决问题!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值