美文网首页
0-1背包问题

0-1背包问题

作者: 景知育德 | 来源:发表于2023-02-24 20:27 被阅读0次

或许在做算法题的老手手里,0-1背包问题算不上什么难事。可以说是最简单的背包问题了。
不过我之前还真没有写过0-1背包问题。
我不去仔细介绍0-1背包问题了。OI Wiki上介绍的很好:
https://oi-wiki.org/dp/knapsack/

二维动态规划

我们用f_{i, j}来表示面对前i个物品时,容量为j的背包可以最多装多少价值的物品。
对于当前(第i个)物品,如果我们不选择此物品,那么
f_{i, j} = f_{i-1, j}
如果我们挑选它,它需要占据背包的重量为w_i,但是它产生了价值v_i,那么
f_{i, j} = f_{i-1, j - w_i} + v_i
我们在选与不选中做抉择,让能产生的价值最大化,于是就有了转移方程:
f_{i, j} = \max( f_{i-1, j},\ f_{i-1, j - w_i} + v_i)
如果我们用二维数组dp来表示,在程序里,转移方程就是

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

有了关键的转移方程之后,补全两次循环,就得到了背包问题的代码。

    private static int knapsack(int[] values, int[] weights, int limit) {
        int n = values.length;
        int[][] dp = new int[n][limit + 1];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j <= limit; j++) {
                if (i == 0) {
                    dp[i][j] = j >= weights[i] ? values[i] : 0;
                }
                if (i > 0 && j >= weights[i]) {
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weights[i]] + values[i]); // 转移方程
                } else if (i > 0) {
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
        return dp[n - 1][limit];
    }

值得注意的是,这两次循环都是正向循环,都是从0开始逐渐递增。(后文并非如此)
二维数组、两层循环,不难得知时间复杂度O(mn),空间复杂度O(nm) 。(n个物品、背包容量为m。)

一维动态规划

可以用滚动数组的思路,把dp这个二维数组,变成一维数组,而节约空间复杂度。
看之前的转移方程
f_{i, j} = \max( f_{i-1, j},\ f_{i-1, j - w_i} + v_i)
其中i这个维度上,只需要ii-1,所以可以用滚动数组,去掉i这一维度。新的转移方程
f_{j} = \max( f_{j},\ f_{j - w_i} + v_i)
那么有的人就会写出如下代码:

    /* 错误示例! */
    private static int knapsack2(int[] values, int[] weights, int limit) {
        int n = values.length;
        int[] dp = new int[limit + 1];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j <= limit; j++) {
                //...
                if (j >= weights[i]) {
                    dp[j] = Math.max(dp[j], dp[j - weights[i]] + values[i]); // 转移方程
                }
            }
        }
        return dp[limit];
    }

这个错误的原因在于,在j的循环过程中,一边循环,一边改变了dp[j]的值。这相当于一种物品可以被购买多次。正确的做法是从大到小遍历j

    private static int knapsack2(int[] values, int[] weights, int limit) {
        int n = values.length;
        int[] dp = new int[limit + 1];
        for (int i = 0; i < n; i++) {
            for (int j = limit; j >= weights[i]; j--) {
                dp[j] = Math.max(dp[j], dp[j - weights[i]] + values[i]); // 转移方程
            }
        }
        return dp[limit];
    }

使用一维数组,还省去了关于i、j取值的判断,整体代码整洁。
时间复杂度O(mn),空间复杂度O(m) 。空间复杂度得以优化。

完全背包问题

完全背包问题中,同一种物品可以被购买多次。实际上就是上文中的“错误”所在。只需要变更j为正序循环,就可以解决完全背包问题。

    private static int completeKnapsack(int[] values, int[] weights, int limit) {
        int n = values.length;
        int[] dp = new int[limit + 1];
        for (int i = 0; i < n; i++) {
            for (int j = weights[i]; j <= limit; j++) { // 注意逆序
                dp[j] = Math.max(dp[j], dp[j - weights[i]] + values[i]); // 转移方程
            }
        }
        return dp[limit];
    }

相关文章

  • Algorithm进阶计划 -- 动态规划(下)

    经典动态规划背包问题最长子序列问题 1. 背包问题 1.1 0-1 背包问题 0-1 背包问题,描述如下: 上面...

  • 背包问题

    背包问题属于典型的动态规划问题。这里我们将详细介绍0-1背包,完全背包和多重背包问题 一、 0-1背包 有N件物品...

  • 算法-动态规划-背包问题

    背包问题是基础的动态规划问题,包含了0-1背包,完全背包,多重背包等。 0-1背包 存在容量为 的背包 , 件体...

  • 背包问题

    1、前言 背包问题是典型的动态规划问题,它有非常多的类型,本文讨论最常见的0-1背包问题 0-1背包问题描述:有一...

  • (python实现)购物单问题

    购物单问题实际上是0-1问题,在解决这个问题之前,要理解0-1背包问题。可以自己百度或者阅读我对0-1背包问题的理...

  • 动态规划

    0-1背包问题 自己实现的0-1背包问题的动态规划解法,先贴上吧,动态规划原理解释有空再写。

  • 动态规划常见面试题

    子序列类型编辑距离 最长递增子序列 最大子数组 最长公共子序列 背包问题0-1背包问题 子集背包问题 完全背包问题...

  • 编程

    今天用0-1算法,编写了背包问题!

  • LeetCode 动态规划专题 5:0-1 背包问题

    这一节我们介绍使用动态规划解决的一个非常经典的问题:0-1 背包问题。 0-1 背包问题描述 问题描述: 有一个背...

  • 数据结构与算法笔记day23:动态规划

    1初识动态规划 这节课的内容不涉及动态规划的理论,而是通过两个例子:0-1背包问题、0-1背包问题升级...

网友评论

      本文标题:0-1背包问题

      本文链接:https://www.haomeiwen.com/subject/utbxldtx.html