动态规划-背包问题

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

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

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)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • Java内存管理-探索Java中字符串String(十二)

    做一个积极的人编码、改bug、提升自己我有一个乐园,面向编程,春暖花开!文章目录一、初识String类二、字符串的不可变性三、字符串常量池和 intern 方法四、面试题1、 String s1 = new String(“hello”);这句话创建了几个字符串对象?2、有时候在面试的时候会遇到这样的问题:**都说String是不可变的,为什么我可以这样做呢,String a = “1”…

    2022年2月28日
    40
  • ostaskcreate函数作用_认识上中下

    ostaskcreate函数作用_认识上中下OSTaskCreate()是学习ucos-Ⅱ操作系统的第一个函数,费了九牛二虎之力,现在感觉差不多可以过了。#ifOS_TASK_CREATE_EN>0INT8UOSTaskCreate(void(*task)(void*p_arg),void*p_arg,OS_STK*ptos,INT8Uprio)/*1*/{ OS_STK…

    2025年9月17日
    5
  • 选择排序算法详解_八大排序算法图解

    选择排序算法详解_八大排序算法图解选择排序就是从待排序的元素中选择最小(最大)的元素,将其放在有序序列的相应位置,使这些元素构成有序序列。选择排序主要有两种:简单选择排序和堆排序。【简单选择排序】编写算法,要求使用简单选择排序算法对元素65、32、71、28、83、7、53、49进行从小到大排序。【算法思想】简单选择排序是一种简单的选择类排序算法,它的基本思想描述如下:假设待排序的元素有n个,在第一趟排序过程…

    2025年7月8日
    3
  • Python列表(list)及其常用方法

    Python列表(list)及其常用方法列表(list):也是有序的数据集合,支持增删查改。用[]来表示列表类型,数据项之间用逗号来分割,列表中的数据项可以是任何类型(Python的特点),数据项可以变化,内存地址不会改变。支持索引和切片

    2022年7月3日
    34
  • django日志配置_怎么更改安装路径

    django日志配置_怎么更改安装路径Django日志信息路径的设置

    2022年4月21日
    75
  • 16G kingston U盘 解除写保护[通俗易懂]

    16G kingston U盘 解除写保护[通俗易懂]前些天买的16Gkingstonu盘忽然有了写保护,但是拆开u盘又没有看到有写保护开关。纠结加郁闷。然后一天后又忽然发生了电脑无法识别U盘,连盘符也读不出来了。里面好多资料全无,彻底让我伤心了,

    2025年10月16日
    5

发表回复

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

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