动态规划——01背包问题、完全背包问题

简介: 动态规划——01背包问题、完全背包问题

01背包问题


1.题目



2.思路分析


先来理解一下题意,假如你来到了一个藏宝洞前,然后手里有一个背包,面前有很多金银珠宝,数量为 n,而你的背包容量有限为 v,你想怎么装,价值最大。


那么定义一个二维数组 f[i][j] ,其含义是前 i 个物品里 体积不超过 j且价值最大的集合。


既然集合有了,那么如何求这个最大值呢,我们把这个背包分为两种情况,包含第 i个物品,和不包含第 i个物品,首先考虑不包含的情况下,那么这个数组价值最大值应该是 f[i-1][j] 而包含i的数组无法直接求,这时候采取曲线救国,他的价值应该是不包含 i的情况下的最大值加上 i的价值,即 f[i][j] =f[i-1][j-v[i] ] +w[i] ,前提是 j 比 v[i]要大 ,那么这个时候的最大值就出来了 f[i][j] =max( f[i-1][j] ,f[i-1][j-v[i] ] +w[i]


3.完整代码


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class Main {
    static int N=1010;
    static int []v=new int[N];   //体积
    static int []w=new int[N];   //价值
    static int [][]f=new int[N][N];  //在体积 j内前 i个物品的最大价值
    public static void main(String[] args) throws IOException {
        BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
        String []s=br.readLine().split(" ");
        int n=Integer.parseInt(s[0]),m=Integer.parseInt(s[1]);
        for (int i = 1; i <=n; i++) {
            String []s1=br.readLine().split(" ");
            v[i]=Integer.parseInt(s1[0]);
            w[i]=Integer.parseInt(s1[1]);
        }
        for (int i = 1; i <=n; i++) {
            for (int j = 1; j <=m; j++) {
                f[i][j]=f[i-1][j]; //  当前背包容量装不进第i个物品,则价值等于前i-1个物品
                if(j>=v[i]) {
                    f[i][j]=Math.max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
                }
            }
        }
        System.out.println(f[n][m]);
    }
}


4.代码优化(二维变一维)


二维转化为一维:


删掉了第一维:在前i个物品中取。


f[j]表示:拿了总体积不超过j的物品,最大总价值。


为何能转化为一维?


二维时的更新方式:f[i][j] =max( f[i - 1][j] , f[i - 1][j - v[i]] + w[i]);


1.我们发现,对于每次循环的下一组i,只会用到 i-1 来更新当前值,不会用到 i-2及之前值。于是可以在这次更新的时候,将原来的更新掉,反正以后也用不到。


所以对于 i的更新,只需用一个数组,直接覆盖就行了。


2.我们发现,对于每次 j的更新,只需用到之前 i-1时的 j或者 j-v[i],不会用到后面的值。


所以为了防止串着改,我们采取从后往前更新的方式,用原来 i-1的数组来更新i。


(如果从前往后更新的话,前面的更新过之后,会接着更新后面的值,这样就不能保证是用原来i-1的数组来更新i的了)


如何转化为一维呢?


只用一个数组,每次都覆盖前面的数组。


1.如果当前位置的东西不拿的话,和前一位置的信息(原来i-1数组的这个位置上的值)是相同的,所以不用改变。


2.如果当前位置的东西拿了的话,需要和前一位置的信息(原来i-1数组的这个位置上值)取max。


所以,更新方式就为: f[j]=max( f[j], f[ j- v[i] ]+ w[i]);


整个更新方式就相当于:


每次i++,就从后往前覆盖一遍f数组,看每个位置上的值是否更新。


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;
public class Main {
    static int N=1010;
    static int n,m;
    static int []v=new int[N];   //体积
    static int []w=new int[N];   //价值
    static int []f=new int[N];  //拿了总体积不超过j的情况下的价值最大
    public static void main(String[] args) throws IOException {
        BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
        String s[]=br.readLine().split(" ");
        n=Integer.parseInt(s[0]);
        m=Integer.parseInt(s[1]);
        for (int i = 1; i <=n; i++) {
          String st[]=br.readLine().split(" ");
          v[i]=Integer.parseInt(st[0]);
          w[i]=Integer.parseInt(st[1]);
        }
        for (int i = 1; i <=n; i++) {
            for (int j = m; j >= v[i]; j--) {
                    f[j]=Math.max(f[j] , f[j-v[i]] +w[i] );
            }
        }
        System.out.println(f[m]);
    }
}


完全背包问题


1.题目



2.思路分析


思路和01背包相同,只是现在一个物品可以取多次了,那么就加次循环,模拟被取多次


3.完整代码


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;
public class Main {
    static int N=1010;
    static int n,m;
    static int []v=new int[N];   //体积
    static int []w=new int[N];   //价值
    static int [][]f=new int[N][N];  //拿了总体积不超过j的情况下的价值最大
    public static void main(String[] args) throws IOException {
        BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
        String s[]=br.readLine().split(" ");
        n=Integer.parseInt(s[0]);
        m=Integer.parseInt(s[1]);
        for (int i = 1; i <=n; i++) {
          String st[]=br.readLine().split(" ");
          v[i]=Integer.parseInt(st[0]);
          w[i]=Integer.parseInt(st[1]);
        }
        for (int i = 1; i <=n; i++) {
            for (int j = 1; j <=m; j++) {
              for(int k=0 ; k* v[i]<=j ;k++){
                  f[i][j]=Math.max(f[i][j] ,f[i-1][j -k*v[i]]+ k*w[i]);
              }
            }
        }
        System.out.println(f[n][m]);
    }
}


4.代码优化

我们列举一下更新次序的内部关系:


f[i , j ] = max( f[i-1,j] , f[i-1,j-v]+w , f[i-1,j-2*v]+2*w , f[i-1,j-3*v]+3*w , .....)

f[i , j-v]= max( f[i-1,j-v] , f[i-1,j-2*v] + w , f[i-1,j-3*v]+2*w , .....)

由上两式,可得出如下递推关系:

f[i][j]=max(f[i,j-v]+w , f[i-1][j])


有了上面的关系,那么其实k循环可以不要了,核心代码优化成这样:


for (int i = 1; i <=n; i++) {
for (int j = 1; j <=m; j++) {
f[i][j]=f[i-1][j];
if(j>=v[i])
f[i][j]=Math.max(f[i][j] ,f[i][j-v[i]]+w[i]);
}
}

这个代码和01背包的非优化写法很像啊!!!我们对比一下,下面是01背包的核心代码


for (int i = 1; i <=n; i++) {
for (int j = m; j >= v[i]; j--) {
f[j]=Math.max(f[j] , f[j-v[i]] +w[i] );
}
}

两个代码其实只有一句不同(注意下标)


f[i][j] = max(f[i][j], f[i-1][j-v[i]]+w[i]); //01背包


f[i][j] = max(f[i][j], f[i][j-v[i]]+w[i]); //完全背包问题


因为和01背包代码很相像,我们很容易想到进一步优化。核心代码可以改成下面这样


for (int i = 1; i <=n; i++) {
for (int j = v[i]; j <=m; j++) {
f[j]=Math.max(f[j] ,f[j-v[i]]+w[i]);
}
}

综上所述,完全背包的最终写法如下:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;
public class Main {
    static int N=1010;
    static int n,m;
    static int []v=new int[N];   //体积
    static int []w=new int[N];   //价值
    static int []f=new int[N];  //拿了总体积不超过j的情况下的价值最大
    public static void main(String[] args) throws IOException {
        BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
        String s[]=br.readLine().split(" ");
        n=Integer.parseInt(s[0]);
        m=Integer.parseInt(s[1]);
        for (int i = 1; i <=n; i++) {
          String st[]=br.readLine().split(" ");
          v[i]=Integer.parseInt(st[0]);
          w[i]=Integer.parseInt(st[1]);
        }
        for (int i = 1; i <=n; i++) {
            for (int j = v[i]; j <=m; j++) {
                    f[j]=Math.max(f[j] ,f[j-v[i]]+w[i]);
            }
        }
        System.out.println(f[m]);
    }
}


5时间复杂度


感谢你能看完, 如有错误欢迎评论指正,有好的思路可以交流一波,如果对你有帮助的话,点个赞支持下
相关文章
|
2月前
|
算法
动态规划之完全背包
本文详解完全背包问题:作为动态规划经典题型,区别于01背包(每物限选1次),其特点是每种物品可无限次选取。文章从定义、状态转移方程(dp[i][j] = max(dp[i-1][j], dp[i][j-w]+v))、二维/一维实现到遍历顺序对组合数与排列数的影响,结合零钱兑换II、组合总和IV等5道典型例题深入剖析,助力掌握核心思想与编码技巧。
250 1
|
人工智能 监控 测试技术
利用AI辅助工具提升软件测试效率
【2月更文挑战第17天】 随着科技的不断发展,人工智能(AI)在各个领域的应用越来越广泛。在软件测试领域,AI技术也发挥着重要作用。本文将探讨如何利用AI辅助工具提升软件测试效率,包括自动化测试、智能缺陷识别和预测等方面。通过引入AI技术,软件测试过程将变得更加高效、准确和可靠。
1118 1
|
存储 缓存 安全
一文讲透认证授权的那些事
权限管理一直都是初级程序员学习的一大重点,也是一大难点,有单点登录,有联合登录,有session有Token,有各种权限框架,还有什么是RBAC,以及分布式下如何做权限管理。
1299 0
|
8月前
|
机器学习/深度学习 存储 算法
动态规划算法深度解析:0-1背包问题
0-1背包问题是经典的组合优化问题,目标是在给定物品重量和价值及背包容量限制下,选取物品使得总价值最大化且每个物品仅能被选一次。该问题通常采用动态规划方法解决,通过构建二维状态表dp[i][j]记录前i个物品在容量j时的最大价值,利用状态转移方程避免重复计算子问题,从而高效求解最优解。
883 1
|
自然语言处理 监控 Cloud Native
精华推荐 |【深入浅出Sentinel原理及实战】「原理探索专题」完整剖析Alibaba微服务架构体系之轻量级高可用流量控制组件Sentinel(1)
精华推荐 |【深入浅出Sentinel原理及实战】「原理探索专题」完整剖析Alibaba微服务架构体系之轻量级高可用流量控制组件Sentinel(1)
2065 0
精华推荐 |【深入浅出Sentinel原理及实战】「原理探索专题」完整剖析Alibaba微服务架构体系之轻量级高可用流量控制组件Sentinel(1)
|
7月前
|
人工智能 自然语言处理 测试技术
从人工到AI驱动:天猫测试全流程自动化变革实践
天猫技术质量团队探索AI在测试全流程的落地应用,覆盖需求解析、用例生成、数据构造、执行验证等核心环节。通过AI+自然语言驱动,实现测试自动化、可溯化与可管理化,在用例生成、数据构造和执行校验中显著提效,推动测试体系从人工迈向AI全流程自动化,提升效率40%以上,用例覆盖超70%,并构建行业级知识资产沉淀平台。
从人工到AI驱动:天猫测试全流程自动化变革实践
|
2月前
|
人工智能 Linux API
OpenClaw(Clawdbot)简中攻略:阿里云/本地部署+API配置+核心Skill集成及避坑手册
OpenClaw(原Clawdbot)作为开源AI Agent领域的标杆工具,凭借“轻量灵活、功能全面、全平台兼容”的特性,成为个人与团队提升效率的核心选择。但现有教程多分散于不同平台,且缺乏针对中文用户的系统化指南,导致新手常面临“部署难、配置乱、用不精”的问题。
1169 4
|
7月前
|
数据采集 存储 人工智能
从0到1:天猫AI测试用例生成的实践与突破
本文系统阐述了天猫技术团队在AI赋能测试领域的深度实践与探索,讲述了智能测试用例生成的落地路径。
从0到1:天猫AI测试用例生成的实践与突破
|
4月前
|
算法 NoSQL Java
拒绝服务雪崩!4种经典限流算法图文详解(附Java实战代码)
限流是保护系统的“保险丝”,防止突发流量导致服务雪崩。常见算法有:固定窗口(简单但有突刺)、滑动窗口(精准平滑)、漏桶(恒定处理速率)和令牌桶(允许突发,最常用)。单机限流可用计数器或Guava,分布式场景则依赖Redis实现全局控制。
964 9
|
SQL 安全 关系型数据库
【MySQL基础篇】事务(事务操作、事务四大特性、并发事务问题、事务隔离级别)
事务是MySQL中一组不可分割的操作集合,确保所有操作要么全部成功,要么全部失败。本文利用SQL演示并总结了事务操作、事务四大特性、并发事务问题、事务隔离级别。
5710 56
【MySQL基础篇】事务(事务操作、事务四大特性、并发事务问题、事务隔离级别)