从背包问题优化详解动态规划思想

动态规划:

所有的数据结构与算法的理解必须建立在题目的练习上,否则看多少理论都没有实际用处!!!

所以下面这些理论文字看不懂通通没关系,跟随下面的背包问题还会跟深入的理解。

一、基本概念:任何数学递推公式都可以直接转换成递归算法,但是基本现实是编译器常常不能正确对待递归算法,结果导致低效的程序。当怀疑很可能是这种情况是,我们必须给编译器提供一些帮助,将递归算法重新写成非递归算法让后者把这些子问题的答案系统地记录在一个表内。利用这种方法的一种技巧叫做动态规划。根据《数据结构与算法分析--Java语言描述》原书第三版 中给动态规划(DP)的定义(出自《数据结构与算法分析--Java语言描述》原书第三版 


二、主要分类:动态规划一般可分为线性动规,区域动规,树形动规,背包动规四类。
举例:
线性动规:拦截导弹,合唱队形,挖地雷,建学校,剑客决斗等;
区域动规:石子合并, 加分二叉树,统计单词个数,炮兵布阵等;
树形动规:贪吃的九头龙,二分查找树,聚会的欢乐,数字三角形等;
背包动规:01背包问题,完全背包问题,分组背包问题,二维背包,装箱问题,挤牛奶等;
除此之外还有许多变形的动态规划问题,再次不一一列举。


三、动态规划问题中的术语(这一部分看不懂没关系,用处不大)

阶段:把所给求解问题的过程恰当地分成若干个相互联系的阶段,以便于求解,过程不同,阶段数就可能不同。描述阶段的变量称为阶段变量。
状态:状态表示每个阶段开始面临的自然状况或客观条件,它不以人们的主观意志为转移,也称为不可控因素。在具体题目中,状态就是某阶段的出发位置,它既是该阶段某路的起点,同时又是前一阶段某支路的终点。
无后效性:我们要求状态具有下面的性质:如果给定某一阶段的状态,则在这一阶段以后过程的发展不受这阶段以前各段状态的影响,所有各阶段都确定时,整个过程也就确定了。状态的这个性质意味着过程的历史只能通过当前的状态去影响它的未来的发展。
决策:一个阶段的状态给定以后,从该状态演变到下一阶段某个状态的一种选择(行动)称为决策。在最优控制中,也称为控制。因状态满足无后效性,故在每个阶段选择决策时只需考虑当前的状态而无须考虑过程的历史。
策略:由每个阶段的决策组成的序列称为策略。集合中达到最优效果的策略称为最优策略。

最优化原理:作为整个过程的最优策略,它满足:相对前面决策所形成的状态而言,余下的子策略必然构成“最优子策略”。一个问题满足最优化原理也称其拥有最优子结构性质。最优性原理实际上是要求问题的最优策略的子策略也是最优


四、基本思想:动态规划思想通常用于求解具有某种最优性质的问题。在这类问题中,可能会有许多可行解。其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。但是适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算。我们可以用一个表来记录所有已解的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。


五、解题思路:

1.找出最优解的性质,刻画其结构特征和最优子结构特征;
2.递归地定义最优值,刻画原问题解与子问题解间的关系,找到状态方程
3.以自底向上的方式计算出各个子问题最优解,并避免子问题的重复计算
4.根据计算最优值时得到的信息,构造最优解。




01背包

问题描述:

有N件物品和一个容量为C的背包。第i件物品的体积是v[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。

 从这个题目中可以看出,01背包的特点就是:每种物品仅有一件,可以选择放或不放

输入:第一行两个数据,物品件数N,背包容量C

接下来N行,每行对应第i件物品的体积v[i]和价值w[i]

Input:

3 9

1 2

2 3

3 1

Output:

6



思路一:逆向规划

我们假定当前阶段的状态d(i, j)表示当前第i层,将从第i个到第n个物品装入容量为j的背包所能达到的最大重量

由此,规划的终点就是d(1, c)[即代表将第1,2,3,...,n个物品装入容量为G的背包中所能达到的最大重量]

规划的起点就是d(n, 0) 或 d(n, c)

如果我们不放物品i,状态转移为d(i+1, j),即将物品i+1放入剩余容量仍为j的背包中的价值

如果我们放入物品i,状态转移为d(i+1, j-v[i])+w[i], 即将物品i+1放入剩余容量为j-v[i]的背包中的价值

状态转移方程:d(i, j) = max{d(i+1, j), d(i+1, j-v[i]) + w[i]}

import java.util.Scanner;public class Main {static Scanner in = new Scanner(System.in);static int[] v,w;static int[][] d;static int n, c;public static void main(String[] args) {n = in.nextInt();c = in.nextInt();v = new int[n+1];w = new int[n+1];d = new int[n+2][c+1];for (int i = 1; i <= n; i++) {v[i] = in.nextInt();w[i] = in.nextInt();}for(int i = n; i >= 1; i--) {for(int j = 0; j <= c; j++) {d[i][j] = (i==n ? 0 : d[i+1][j]);if(j >= v[i]) {d[i][j] = Math.max(d[i][j],d[i+1][j-v[i]]+w[i]);}}}System.out.println(d[1][c]);}
}

思路二:正向规划

我们假定当前阶段的状态d(i, j)表示当前第i层,将前i个物品装入容量为j的背包所能达到的最大重量

规划的起点d(1, 0) 或 d(1, c)

规划的终点d(n ,c)

状态转移方程:d(i, j) = max{d(i-1, j), d(i-1, j-v[i]) + w[i]}



import java.util.Scanner;public class Main {static Scanner in = new Scanner(System.in);static int[] v,w;static int[][] d;static int n, c;public static void main(String[] args) {n = in.nextInt();c = in.nextInt();v = new int[n+1];w = new int[n+1];d = new int[n+1][c+1];for(int i = 1; i <= n; i++) {int v = in.nextInt();int w = in.nextInt();for(int j = 0; j <= c; j++) {d[i][j] = (i==1 ? 0 : d[i-1][j]);if(j >= v) {d[i][j] = Math.max(d[i][j],d[i-1][j-v]+w);}}}System.out.println(d[n][c]);}
}
思路一与思路二区别:
1. 正向规划可以在输入v, w的过程中处理数据,节省空间;在打印结果的时候不方便
2. 逆向规划可以保证打印结果时最小字典序;需要存储v, w




下面引入一个概念
滚动数组:形态上是一个一维数组,其作用在于可以优化空间,主要应用在递推或动态规划中(尤其是在背包问题中)。
由于DP题目是一个自底向上的扩展过程,我们常常需要用到的是连续的解,当一个状态转移到另一个状态时,大多数情况下,之前存储的状态信息已经无用了,往往可以舍去。所以用滚动数组优化是很有效的。
好处:利用滚动数组可以在N很大的情况下达到压缩存储的作用。滚动数组在时间上并没有什么改善,但是能节省很大的空间。
不足:由于没有存储之前的数据,所以在实现打印方面十分困难

思路三:用滚动数组实现

我们假定当前阶段的状态d(j)表示将当前物品装入容量为 j 的背包所能达到的最大重量

规划的起点d( 0 )

规划的终点d( c )

状态转移方程:d( j ) = max{d( j ), d( j - v) + w}

import java.util.Scanner;public class Main {static Scanner in = new Scanner(System.in);static int[] v, w, d;static int n, c;public static void main(String[] args) {n = in.nextInt();c = in.nextInt();v = new int[n+1];w = new int[n+1];d = new int[c+1];for (int i = 1; i <= n; i++) {int v = in.nextInt();int w = in.nextInt();for (int j = c; j >= 0; j--) {if (j >= v) {d[j] = Math.max(d[j], d[j-v] + w);}}}System.out.println(d[c]);}
}

注意:

在这类求最优解的背包问题中,有的题目要求“恰好装满背包”时的最优解,而有的题目则并没有要求必须装满。

区别这两种问法的实现方法是在初始化的时候有所不同。

如果是要求恰好装满背包,那么在初始化时除了f[0]为0其它f[1..V]均设为-∞,这样就可以保证最终得到的f[N]是一种恰好装满背包的最优解。

如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该将 f[0..V]全部设为0

为什么呢?可以这样理解:初始化的f数组事实上就是在没有任何物品可以放入背包时的合法状态。如果要求背包恰好装满,那么初始化时只有容量为0的背包可能被价值为0的物品“恰好装满”,其它容量的背包尚且没有合法的解,属于未定义的状态;如果背包并非必须被装满,那么初始化时任何容量的背包都有一个合法解,即什么都不装。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/569799.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

js判断数组里是否有重复元素的方法

转&#xff1a; js判断数组里是否有重复元素的方法 https://blog.csdn.net/longzhoufeng/article/details/78840974第一种方法&#xff1a;但是下面的这种方法数字字符串类似相同&#xff0c;返回的还是真&#xff0c;有点不靠谱&#xff0c;如果是其它的字符是可以的 var ary1…

深入浅出讲算法思想--蛮力法思想分析及应用

蛮力法&#xff08;brute force method&#xff0c;也称为穷举法或枚举法&#xff09;是一种简单直接地解决问题的方法&#xff0c;常常直接基于问题的描述&#xff0c;所以&#xff0c;蛮力法也是最容易应用的方法。虽然&#xff0c;用蛮力法设计的算法时间特性往往也是最低的…

光耦在短距离通信中的应用

在高低压隔离系统设计中&#xff0c;难免会使用光耦来通信。在选择光耦器件时&#xff0c;需要考虑光耦允许的最大通信速率&#xff0c;否则在高速通信时会失败. 对于高速的光耦应用时&#xff0c;需要注意电流传输比率和开关速度。 参考文档《Basic Characteristics and Appli…

减治法解决八枚硬币问题/假币问题(JAVA)----二分,三分,不知轻重的情况

八枚硬币问题 在八枚外观相同的硬币中&#xff0c;有一枚是假币&#xff0c;并且已知假币与真币的重量不同&#xff0c;但不知道假币与真币相比较轻还是较重。可以通过一架天平来任意比较两组硬币&#xff0c;设计一个高效的算法来检测出这枚假币。 我们先假设一个条件&#xf…

减治法在查找算法中的应用(JAVA)--折半查找

减治法在查找算法中的应用 折半查找&#xff1a;(时间复杂度O(log以2为底n的对数)) 对于有序数组的查找来说&#xff0c;折半查找是一种非常高效的算法&#xff0c;其基本原理为&#xff1a;比较查找键k和数组中间元素a[m]&#xff0c;如果相等&#xff0c;算法结束&#xff…

牛客寒假算法基础集训营2 A处女座的签到题

处女座的签到题 链接&#xff1a;https://ac.nowcoder.com/acm/contest/327/A 题目描述 平面上有n个点&#xff0c;问&#xff1a;平面上所有三角形面积第k大的三角形的面积是多少?输入描述: 第一行T&#xff0c;表示样例的个数。对于每一组样例&#xff0c;第一行两个整数n和…

减治法解决约瑟夫斯问题(JAVA)

减治法在查找算法中的应用问题背景&#xff1a;据说著名犹太历史学家 Josephus有过以下的故事&#xff1a;在罗马人占领乔塔帕特后&#xff0c;39 个犹太人与Josephus及他的朋友躲到一个洞中&#xff0c;39个犹太人决定宁愿死也不要被敌人抓到&#xff0c;于是决定了一个自杀方…

从NetCore报错到MySql安全

从NetCore报错到MySql安全 原文:从NetCore报错到MySql安全之前项目在测试服务器上的一些接口时不时会报出下面的错误&#xff1a;&#xff08;采用Abp框架&#xff09; "SocketException: 你的主机中的软件中止了一个已建立的连接。 STACK TRACE: at MySqlConnector.Pr…

减治法在查找算法中的应用(JAVA)--快速查找

减治法在查找算法中的应用 快速查找&#xff1a;选择问题是求一个n个数列表的第k个最小元素的问题&#xff0c;这个数k被称为顺序统计量。对于k1或kn来说&#xff0c;这并没有什么意义&#xff0c;我们通常会要找出这样的元素&#xff1a;该元素比列表中一半元素大&#xff0…

JavaScript中使用Json

转载于:https://www.cnblogs.com/lyonwu/p/10368989.html

减治法在查找算法中的应用(JAVA)--二叉查找树的查找、插入、删除

减治法在查找算法中的应用二叉查找树的查找与插入&#xff1a; 二叉排序树或者是一棵空树&#xff0c;或者是具有下列性质的二叉树&#xff1a;&#xff08;1&#xff09;若左子树不空&#xff0c;则左子树上所有结点的值均小于或等于它的根节点的值&#xff1b; &#xff08;…

Navicat Premium试用期破解方法(转)

转载网址https://blog.csdn.net/Jason_Julie/article/details/82864187 1、按步骤安装Navicat Premium&#xff0c;如果没有可以去官网下载&#xff1a;http://www.navicat.com.cn/download/navicat-premium 2、安装好后下载激活文件&#xff1a;https://pan.baidu.com/s/1kVgT…

减治法在排序算法中的应用(JAVA)--插入排序

一、减治法在排序算法中的应用 插入排序&#xff1a;时间复杂度O(n^2)&#xff0c;虽然和选择、冒泡在最坏的情况下时间复杂度相同&#xff0c;但是插排平均性能在比自身的最差性能快一倍&#xff0c;所以相比选择、冒泡来说&#xff0c;插排要领先于二者。 public class Main…

减治法在求解拓扑排序问题中的应用(JAVA)--有向无环图

减治法在求解拓扑排序问题中的应用 拓扑排序&#xff1a;对于一个有向无环图来说&#xff0c;如果我们能够按照次序列出顶点&#xff0c;使得对于每条边来说&#xff0c;边的起始顶点总是排在边的结束顶点之前&#xff0c;那么这个过程就称为拓扑排序&#xff0c;拓扑排序有解…

Java中string.equalsIgnoreCase(0)与0.equalsIgnoreCase(string)的区别:

string.equalsIgnoreCase("0")&#xff1a;如果string为null,会抛出java.lang.NullPointerException异常。 "0".equalsIgnoreCase(string)&#xff1a;即使string为null也不会抛出异常。 所以一般如果判断一个字符串与一个常量是否相等的时候&#xff0c;应…

减治法在生成全排列中的应用(JAVA)--回溯、Johnson-Trotter算法、自字典序

减治法在生成组合对象问题中的应用 在深入浅出讲算法思想--蛮力法思想分析及应用这篇文章的最优解问题中中已经初步讲解了这类应用&#xff0c;下面我们将使用减治法再次思考这类问题。 1、全排列问题&#xff0c;在数学中求解一个n个数组合的全排列问题会产生n&#xff01;…

减治法在生成子集问题中的应用(JAVA)--递归、二进制反射格雷码

减治法在生成组合对象问题中的应用 生成子集问题&#xff1a;经典的背包问题就是求解一个最优子集的问题&#xff0c;这里我们来讨论一个更简单的问题。对于任意一个集合来说&#xff0c;它都存在2^n个子集&#xff08;一个集合所有的子集集合称为幂集&#xff09;。 1&…

【第九课】MriaDB密码重置和慢查询日志

目录 1、如何进行修改MariaDB的密码2、Mariadb的慢查询日志1、如何进行修改MariaDB的密码 记得root密码的修改方式&#xff1a; [rootlocalhost ~]# mysqladmin -uroot -p123456 password "123123" [rootlocalhost ~]# mysql -uroot -p Enter password: ERROR 1045 …

减治法解决俄式乘法问题(JAVA)

以上是在《算法设计与分析基础》一书中给出的定义。 这种算法只包括折半、加倍、相加这几个操作&#xff0c;在计算时&#xff0c;不需要用九九乘法表 。 同时&#xff0c;这个方法每次都会将计算的规模减少&#xff0c;运用了减治的思想 public class Main {public static…

1.需要对txt存放的测试数据做去重处理,代码如下

采用集合去重&#xff0c;在新文件里逐行写入&#xff0c;达成目的 old_file "D:/testdata/memberId.txt" #old result_file "D:/testdata/memberId_new.txt" #new lines_seen set() out_file open(result_file, "w") f open(old_file, &q…