【算法】动态规划,解决背包问题(knapsack)(小偷是不是也得学一学动态规划啦哈哈)

大家好,我是被白菜拱的猪。

一个热爱学习废寝忘食头悬梁锥刺股,痴迷于girl的潇洒从容淡然coding handsome boy。

一、写在前言

常见算法思想第二谈—动态规划。

二、动态规划

(一)概念介绍

动态规划(Dynamic Programmming)是将大问题拆解成小问题,然后一步步获得最优解的办法。

看到这里我们不免产生疑惑,这不是与前面讲的分治算法一样嘛,都是将大问题拆分成小问题,他们的区别就是分值算法拆出的小问题是相互独立的,小问题的解不影响大问题的解,而动态规划经过分解的子问题不是相互独立的,它下一阶段的解是建立在上一阶段解的基础上进一步求解。所以称之为动态规划,它的解在动态的变化。

解决动态规划问题可以通过画图填表的方式来推导出最优解。

(二)01背包问题

1、场景再现

背包问题是动态规划的典型问题,我们通过解决该问题来加深对动态规划的理解。

背包问题开个玩笑讲就是小偷拿一个包去偷东西,包的容量是是不变的,每一件都东西有它的重量和价值,那么请问小偷怎么偷能让自己偷的东西价值最大,在这里背包问题又分为完全背包问题和01背包问题,完全背包问题就是每一件物品的数量是无限的,而01背包问题是每种物品只有一件。我们这里讲的是01背包问题。

好我们再现一个场景,下面有四种商品。小偷的包容量为8,问小偷怎么偷的money最多。

编号重量价值
123
234
342
456

2、思路讲解

我们把该问题分为两部分,一是如何装东西,另一个是如何打印出装了那些东西(用了回溯)

(1)偷东西

我们画一个表格一步一步的来。首先假如只放第一个物品,背包从0-8,这里总共要考虑32种情况,让我们看一下具体怎么填写这个表格。
在这里插入图片描述
假如看了上面的解题归纳还是没有理解,那我就用的白话在解释一遍,首先我们装一个物品,这时分两种情况,即不能装进去和能装进去,假如不能装进去那么此时最大的价值等于前面已经装过物品价值的总和。
能装进去,下面就要看看我们装了这个物品之后是否导致包内的价值反而变低,因为有的商品可能是体积大,但是价值低,所以我们这就就要进行比较,装与不装前后的价值变化,前面不装是因为客观因素决定,包包没有空间了我们没有办法去装,此时能装但是不去装,他的价值跟前面讲的不装价值一样,那么能装我们去装这时我们该怎么去装呢?首先我们的已经确定了这个商品肯定要装进去的,所以我们看包包减去要装商品体积之后剩下的体积所能装的最大价值,这时从表格里面找就ok了,然后进行比较。

下面根据这个思路去画一画表格,为了观察方便,我们把上面的表格在赋值一份。

编号重量价值
123
234
342
456

物品中的0代表啥也没装

物品 \ 背包容量012345678
0000000000
1003333333
2003447777
3003447777
40034477910

我们设置两个一维数组分别表示物品的重量与价值,代码见代码实现。
先在这里贴打印结果,是不是跟我们之前分析的一摸一样。
在这里插入图片描述

(2)偷了什么东西

我们知道了小偷一个小小的包包能装最大的价值,那么偷了什么东西我们还是不知道,那么我们怎么知道小偷偷了什么东西呢?

我们采用回溯,就是倒着来,从右下角出发,根据装物品的过程,假如前n个物品与前n-1个物品的价值一样,说明该物品没有加入到背包,假如不一样就说明该物品加入到了背包,然后减去该物品的体积,在去判断。直到背包容量为0。
在这里插入图片描述

最后查到2号和4号物品被偷了,具体的代码全都放在了下面。

3、代码实现

ok,有请我们的代码隆重登场。一定好好看哦。

package com.codingboy.algorithm.dynamic;

import java.util.Arrays;

/**
 * @author: ljl
 * @date: 2020/9/3 18:17
 * @description: 动态规划求解背包问题
 */
public class Knapsack {
    public static void main(String[] args) {
        Knapsack knapsack = new Knapsack();
        knapsack.dynamic();
        knapsack.print();
        //从右下角开始查找
        knapsack.find(4,8);
    }




    private int[] weight = {0,2,3,4,5};
    private int[] value = {0,3,4,2,6};
    private int[][] table = new int[weight.length][9];
    //用来表示哪一个物品被装了进去 0-没有 1-有
    private int[] product = new int[5];


    public void dynamic() {
        //外层是商品的数量
        for (int i = 1; i < 5; i++) {
            //里面是背包的数量,背包的容量为8
            for (int j = 1; j < 9; j++) {
                //先判断是否能讲该商品装进去
                if (weight[i] > j) {  //装不进去,价值与前面n-1物品价值一样
                    table[i][j] = table[i-1][j];
                }else { //能装进去,判断是否因加了使价值变小,取加不加两种情况的最大值
                    //table[i - 1][j - weight[i]] + value[i] 注意要好好理解这段代码所表达的意思
                    table[i][j] = Math.max(table[i-1][j],table[i - 1][j - weight[i]] + value[i]);
                }
            }
        }
    }

    //打印表格
    public void print() {
        for (int i = 0; i < table.length; i++) {
            //里面是背包的数量,背包的容量为8
            for (int j = 0; j < table[0].length; j++) {
                System.out.print(table[i][j] + "  ");
            }
            System.out.println();
        }
    }

    //i,j指的是表的坐标 i指的是物品,j指的是宝宝的容量
    public void find(int i,int j) {
        if (i == 0) { //就输出哪些装了哪些没装
            //直接打印
            System.out.println(Arrays.toString(product));
            return;
        }
        //然后去判断是否含有该物品
        if (table[i][j] == table[i-1][j]) {
            //说明不含这个物品,设置为0
            product[i] = 0;
            //然后继续查找
            find(i - 1,j);
        } else { //要么大要么相等,不存在下面比上面小的情况,所以这里使用else
            //说明包含这个物品
            product[i] = 1;
            //然后减去当前物品的容量继续查找
            find(i - 1,j - weight[i]);
        }
    }


}

三、结束语

写下此篇文字,看来小偷偷东西学学动态规划还是很有必要的哈哈哈哈哈哈,开个玩笑。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值