动态规划-背包问题

动态规划-背包问题背包问题是一种组合优化的NP完全问题。有N个物品和容量为W的背包,每个物品都有自己的体积w和价值v,求拿哪些物品可以使得背包所装下的物品的总价值最大。如果限定每种物品只能选择0个或1个,则问题称为0-1背包问题;如果不限定wu’pi…


Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

背包问题是一种组合优化的NP完全问题。有N个物品和容量为W的背包,每个物品都有自己的体积w和价值v,求拿哪些物品可以使得背包所装下的物品的总价值最大。如果限定每种物品只能选择0个或1个,则问题称为0-1背包问题;如果不限定物品的数量,则问题称为“无界背包或完全背包”问题。

一、0-1背包问题

可以用动态规划来解决背包问题。以0-1背包为例。我们可以定义一个二维数组dp存储最大值,其中dp[i][j]表示前i件物品体积不超过j的情况下能达到的最大价值;如果我们将物品i放入背包,假设第i件物品体积为w,价值为v,那么我么能得到dp[i][j] = dp[i – 1][j – w] + v, 只需要在便利过程中对着两种情况取最大值即可,总时间和空间复杂度都为O(NW)。

int knapsack(vector<int> weights, vector<int> values, int N, int W) {
    vector<vector<int>> dp(N + 1, vector<int>(W + 1, 0));
    for (int i = 1; i <= N; i++) {
        int w = weights[i - 1], v = values[i - 1];
        for (int j = 1; j <= W; j++) {
            if (j >= w) {
                dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w] + v);
            }
            else {
                dp[i][j] = dp[i - 1][j];
            }
        }
    }
    return dp[N][W];
}

动态规划-背包问题

0-1背包问题-状态转移矩阵样例

 

可以进一步对0-1背包进行空间优化,将复杂度降低为O(W)。如图所示,假设我们目前考虑物品i=2, 且其体积为w=2, 价值为v=3;对于背包容量j,我们可以得到dp[2][j] = max(dp[1][j], dp[1][j – 2] + 3)。 可以发现我们永远只依赖于上一排i=1的信息,之前算过的其他物品都不需要再使用。因此我们可以去掉dp矩阵的第一个维度,在考虑物品i时变成dp[j] = max(dp[j], dp[j-w] + v)。 这里注意需要逆向便利,这样才能够调用上一行物品i-1时 dp[j-w]的值;若按照从左往右的顺序进行正向遍历,则dp[j-w]的值在遍历到j之前就已经背更新成物品i的值了。

int knapsack(vector<int> weights, vector<int> values, int N, int W) {
    vector<int> dp(W + 1, 0);
    for (int i = 1; i <= N; ++i) {
        int w = weights[i - 1], v = values[i - 1];
        for (int j = W; j >= w; j--) {
            dp[j] = max(dp[j], dp[j - w] + v);
        }
    }
    return dp[W];
}

 

二、完全背包问题

完全背包问题中,一个物品可以拿多次。如下图所示:

动态规划-背包问题

假设我们便利到物品i=2, 且其体积为w=2, 价值为v=3; 对于背包容量j = 5, 组多只能装下2个该物品。那么我们的状态转移方程就变成了dp[2][5] = max(dp[1][5], dp[1][3] + 3, dp[1][1] + 6)。 如果采用这种方法,假设背包容量无穷大而物体的体积无穷小,我们这里的比较次数也会趋近于无穷大,远超O(NW)的时间复杂度。

怎么解决这个问题呢?我们发现在dp[2][3]的似乎和其实已经考虑过了dp[1][3]和dp[2][1]的情况,而在dp[2][1]的时候也已经考虑过dp[1][1]的情况。因此如下图所示:

动态规划-背包问题

对于拿多个物品的情况,我们只需要考虑dp[2][3]即可,即dp[2][5] = max(dp[1][5], dp[2][3]+3). 这样就得到了完全背包问题的状态转移方程:

dp[i][j] = max(dp[i – 1][j], dp[i][j-w] +v), 其与0-1背包问题的差别仅仅是把状态转移方程中的第二个i-1变成了i。

int knapsack(vector<int> weights, vector<int> values, int N, int W) {
    vector<vector<int>> dp(N + 1, vector<int>(W + 1, 0));
    for (int i = 1; i <= N; i++) {
        int w = weights[i - 1], v = values[i - 1];
        for (int j = 1; j <= W; j++) {
            if (j >= w) {
                dp[i][j] = max(dp[i - 1][j], dp[i][j - w] + v);
            }
            else {
                dp[i][j] = dp[i - 1][j];
            }
        }
    }
    return dp[N][W];
}

同样的,我们也可以利用空间压缩将时间复杂度降低为O(W)。这里注意哦我们在便利每一行的时候必须正向便利,因为我们要利用当前物品在第j-w列的信息。

int knapsack(vector<int> weights, vector<int> values, int N, int W) {
    vector<int> dp(W + 1, 0);
    for (int i = 1; i <= N; i++) {
        int w = weights[i - 1], v = values[i - 1];
        for (int j = w; j <= W; j++) {
            dp[j] = max(dp[j], dp[j - w] + v);
        }
    }
    return dp[W];
}

注意:压缩空间到底需要正向还是逆向遍历呢?物品和体积哪个放在外曾,哪个放在内层呢?这取决与状态转移方程的依赖关系。在思考空间压缩之前,不妨将转移矩阵画出来,方便思考如何进行空间压缩。

如果实在不想仔细思考,有个简单的口诀:0-1背包对物品的迭代放外层,里层的体积或值逆向遍历;完全背包对物品的迭代放里层,外层的体积或价值正向遍历。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/164398.html原文链接:https://javaforall.net

赞 (0)
全栈程序员-站长的头像全栈程序员-站长

相关推荐

  • es6之Promise是什么「建议收藏」

    es6之Promise是什么「建议收藏」Promise的含义Promise是一个容器,内部保存着某个未来才会结束的事件(通常是一个异步操作)的结果。Promise也是一个对象,可以通过这个对象获取异步操作的消息。Promise的特点:对象的状态不受外部影响,只有异步操作的结果才能决定状态。一共有三种状态:pending(进行中)、fulfilled(成功的)和rejected(失败的)。对象的状态发生改变后,不会再变化,并且随时可以得到这个结果。对象的状态改变只有两种情况:pending=》fulfilled,pending=》reje

    2022年5月29日
  • file write error怎么解决_error attempt to read or write

    file write error怎么解决_error attempt to read or write做64位程序dll注入时候出现 WriteProcessMemory的299错误,查找错误代码解释为“仅完成部分的ReadProcessMemory或WriteProcessMemory请求。”完全搞不清楚怎么回事,因为调用了两次 WriteProcessMemory,第一次正常,第二次调用出错, 最后发现 release模式不会出错,然后对比,发现关闭“启动增量连接” 可

    2022年9月12日
  • 批量给图片重命名_图片快速重命名编号

    批量给图片重命名_图片快速重命名编号如果你是一名摄影师,又或者你是一名图片设计的工作人员,工作中肯定会面对很多图片文件,图片多了就需要进行整理,不然就很难区分和管理,就很不利于我们的使用。为了更好的整理往往我们就需要给图片进行重命名并且排序,这就出现一个问题了,如何批量重命名这些图片并且进行编号呢?如果你还不知道如何解决这个问题,那么你就要跟随小编的步伐,我来为大家详细介绍图片批量重命名编号的方法吧!需要使用的软件:优速文件批量重命名软件下载地址:免费下载优速文件批量重命名软件https://www.yososoft.com/do

    2022年9月5日
  • jboss项目文件路径_java读取一个文件夹下的所有文件

    jboss项目文件路径_java读取一个文件夹下的所有文件==========转自:https://blog.csdn.net/tomcat_2014/article/details/50624120===========JavaWeb中读取文件资源的路径问题转载 2016年02月02日15:34:4916757在做javaweb开发的时候,我们可能会需要从本地硬盘上读取某一个文件资源,或者修改某一个文件,这个时候就需要先找到这个文件,然后用FileI…

    2022年9月19日
  • pycharm中pip安装地址更改_pycharm怎么安装pip库

    pycharm中pip安装地址更改_pycharm怎么安装pip库pycharm修改pip源为清华源-提高下载速度让PIP源使用国内镜像,提升下载速度和安装成功率。以后文章更新在https://oldpan.me国内pip源:清华大学https://pypi.tuna.tsinghua.edu.cn/simple/在使用pycharm时,…

    2022年8月28日
  • 树莓派python编程入门与实战_树莓派python软件

    树莓派python编程入门与实战_树莓派python软件一、远程连接和远程桌面设置:终端下执行命令:ifconfig,查看树莓派的IP终端下执行命令:sudoapt-getinstallxrdp,安装远程桌面支持。在windows电脑上点开始,输入mstsc,出来远程桌面界面,输入树莓派IP,用户名:pi密码raspberry,就可以连上了。二、关闭树莓派正确操作:方式一:终端下执行命令:sudopoweroff…

    2022年10月15日

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号