Java使用动态规划算法思想解决背包问题

 更新时间:2022年04月29日 10:05:46   作者:LNORA  
背包问题(Knapsack problem)是一种组合优化的NP完全问题。问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高

动态规划算法

动态规划算法的思想

动态规划算法处理的对象是多阶段复杂决策问题,动态规划算法和分治算法类似,其基本思想也是将待求解问题分解成若干个子问题(阶段),然后分别求解各个子问题(阶段),最后将子问题的解组合起来得到原问题的解,但是与分治算法不同的是,子问题往往不是相互独立的,而是相互联系又相互区别的

动态规划算法问题求解的目标是获取导致问题最优解的最优决策序列(最优策略)。对于一个决策序列,可以用一个数值函数(目标函数)衡量这个决策的优劣。

最优性原理

动态规划算法的最优性原理:一个最优决策序列具有这样的性质,不论初始状态和第一步决策如何,对前面的决策所形成的状态而言,其余的决策必须按照前一次决策所产生的新状态构成一个最优决策序列。

最优性原理体现为问题的最优子结构特性,对于一个问题,如果能从较小规模的子问题的最优解求得较大规模同类子问题的最优解,最终得到给定问题的最优解,也就是问题的最优解中所包含的子问题的最优解,这种性质被称为最优子结构性质。最优子结构特性使得在从较小问题的解构造较大问题的解时,只需考虑子问题的最优解,然后以自底向上的方式递归地从子问题的最优解逐步构造出整个问题的最优解,它保证了原问题的最优解可以通过求解子问题的最优解来获得,最优子结构的特性是动态规划算法求解问题的必要条件。

动态规划算法的三大特点

  • 如果求解的问题满足最优性原理,则说明用动态规划算法有可能解决该问题,在分析问题的最优子结构时,所使用的方法具有普遍性。要注意一个问题可以有多种方式刻画它的最优子结构,有些表示方法的求解速度更快(空间占用少,问题的维度低)。
  • 递归定义最优解决方案。动态规划的每一步决策都依赖于子问题的解,动态规划算法求解最优化问题的步骤为:找出最优解的结构,具体来说就是看这个问题是否满足最优子结构特性;其次递归定义一个最优解的值,即构造原问题和子问题之间的递归方程,原问题的最优解可以通过子问题的最优解获得。
  • 以自底向上的方式计算出最优解的值(最优解的目标函数的值)。对子问题的分解是基于原问题的分解的基础之上进行的,而且这些子问题的分解过程是相互独立的。在对原问题分解的过程中,会出现大量的共享重叠子问题,为了避免对大量重叠子问题的重复计算,一般动态规划算法从自底向上开始计算,对每一个问题只解一次,并且保存求解子问题的最优值,当再需要求解这个子问题的时候,可以用常数时间查看一下结果,而不是再递归的去求解每一个问题的解,因此提高了动态规划算法的效率。

动态规划算法中的0/1背包问题

0/1背包问题的规则是不允许该物品进行拆分,即只有把物品放入和不放入两个基本状态,要使用动态规划算法求解决如何放物品才可以是背包中的物品的总价值达到最高。

示例

有一个载重为10的背包,现有4类物品,每类物品的重量分别为(w0,w1,w2,w3)=(2,3,4,7),它们的价值分别为(p0,p1,p2,p3)=(1,3,5,9)。试问如何装载能够使背包容纳物品的价值最大。

package 算法设计与分析;
 import java.util.Arrays;
 import java.util.Scanner;
 //m表示的是背包的容量,a表示有多少种类的物品,数组w用与存放每类物品的重量,数组val用于存放每类物品的价值
 public class my {
     public static void main(String[] args) {
         Scanner scanner = new Scanner(System.in);
         System.out.print("请输入背包的容量:");
         int m = scanner.nextInt();
         Scanner inScanner = new Scanner(System.in);
         System.out.print("请输入物品的个数:");
         int a = inScanner.nextInt();
         int[] w = new int[a + 1];
         System.out.print("请输入物品的重量:" + " ");
         for (int i = 1; i <= a; i++) {
             w[i] = inScanner.nextInt();
         }
         int[] val = new int[a+ 1];
         System.out.print("请输入物品的价值:" + " ");
         for (int i = 1; i <= a; i++) {
             val[i] = inScanner.nextInt();
         }
         int n = val.length;
         int[][] path = new int[n +1][m+1 ];
         //创建二维数组
         //v[i][j]:表示在前i个物品中能够装入容量为j的背包中的最大价值
         int[][] v = new int[n +1][m + 1];
         //初始化第一行和第一列
         for (int i = 0; i < v.length; i++) {//v.length:获取二维数组的行数
             v[i][0] = 0;//将第一列设置为0
         }
         for (int i = 0; i < v[0].length; i++) {//v[0].length:获取二维数组的列数
             v[0][i] = 0;//将第一行设置为0
         }
         for (int i = 1; i < v.length; i++) {//int i = 1 不处理第一行
             for (int j = 1; j < v[0].length; j++) {//int j = 1 不处理第一列
                 if (w[i - 1] > j) {
                     v[i][j] = v[i - 1][j];
                 } else {
                     if (v[i - 1][j] < (val[i - 1] + v[i - 1][j - w[i - 1]])) {
                         v[i][j] = val[i - 1] + v[i - 1][j - w[i - 1]];
                         //把当前情况记录到path
                         path[i][j] = 1;
                     } else {
                         v[i][j] = v[i - 1][j];
                     }
                 }
             }
         }
         //输出二维数组:
         for (int[] ints : v) {
             System.out.println(Arrays.toString(ints));
         }
         //输出最后我们是放入的那些商品
         int i = path.length - 1;//行的最大下标
         int j = path[0].length - 1;//列的最大下标
         while (i > 0 && j > 0) {//从path的最后开始找
             if (path[i][j] == 1) {
                 System.out.printf("第%d个商品放入背包\n", i-1);
                 j -= w[i - 1];
             }
             i--;
         }
     }
 }

输入一个背包容量为10,里面有4类物品,物品的重量分别为2,3,4,7,物品的价值分别为1,3,5,9

 结果 

动态规划算法的优点

若要解一个给定问题,我们需要解其不同部分(即子问题),再合并子问题的解以得出原问题的解。 通常许多子问题非常相似,为此动态规划法试图仅仅解决每个子问题一次,从而减少计算量: 一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表。 这种做法在重复子问题的数目关于输入的规模呈指数增长时特别有用。

小结

以上就是针对动态规划算法的详细分析,利用动态规划算法可以避免重复计算多次子问题,提高效率,使计算机的性能更好!

到此这篇关于Java使用动态规划算法思想解决背包问题的文章就介绍到这了,更多相关Java背包问题内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • springboot+thymeleaf国际化之LocaleResolver接口的示例

    springboot+thymeleaf国际化之LocaleResolver接口的示例

    本篇文章主要介绍了springboot+thymeleaf国际化之LocaleResolver的示例 ,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-11-11
  • Java实现学生信息管理系统超详细教程

    Java实现学生信息管理系统超详细教程

    这篇文章主要介绍了一个简单的Java学生信息管理系统,使用ArrayList集合存储学生对象信息,并实现了添加、删除、修改和查看学生信息的功能,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-01-01
  • 详解Java利用实现对称加密(DES、3DES、AES)

    详解Java利用实现对称加密(DES、3DES、AES)

    本篇文章主要介绍了Java利用实现对称加密(DES、3DES、AES),具有一定的参考价值,有兴趣的可以了解一下。
    2017-01-01
  • Spring Cloud 负载均衡器 Ribbon原理及实现

    Spring Cloud 负载均衡器 Ribbon原理及实现

    这篇文章主要介绍了Spring Cloud 负载均衡器 Ribbon原理及实现,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-03-03
  • springboot集成camunda的实现示例

    springboot集成camunda的实现示例

    本文主要介绍了springboot集成camunda的实现示例,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-10-10
  • 学会IDEA REST Client后就可以丢掉postman了

    学会IDEA REST Client后就可以丢掉postman了

    这篇文章主要介绍了学会IDEA REST Client后就可以丢掉postman了,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-12-12
  • Spring Security 实现用户名密码登录流程源码详解

    Spring Security 实现用户名密码登录流程源码详解

    在服务端的安全管理使用了Spring Security,用户登录成功之后,Spring Security帮你把用户信息保存在Session里,但是具体保存在哪里,要是不深究你可能就不知道,今天小编就带大家具体了解一下Spring Security实现用户名密码登录的流程
    2021-11-11
  • Java代码执行顺序——类的初始化场景

    Java代码执行顺序——类的初始化场景

    这篇文章主要为大家介绍了Java代码执行顺序类的初始化场景实例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08
  • SpringMVC中的常用注解和用法举例详解

    SpringMVC中的常用注解和用法举例详解

    在Spring和SpringMVC中,注解的使用大大简化了配置,提高了代码的可读性和可维护性,这篇文章主要给大家介绍了关于SpringMVC中常用注解和用法的相关资料,需要的朋友可以参考下
    2024-08-08
  • 不写mybatis的@Param有的报错有的却不报错问题分析

    不写mybatis的@Param有的报错有的却不报错问题分析

    这篇文章主要为大家介绍了不写mybatis的@Param有的报错有的却不报错问题分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09

最新评论