矿工挖矿问题

矿工挖矿

问题描述

该问题为了解决在给定金矿和矿工数量的前提下,能够获得最多黄金的挖矿策略。很多算法题其实就是这个问题换了一个情境。

有5个金矿,每个金矿黄金储量不同,需要参与挖掘的工人数目也不同,假定有10个工人,每个金矿要么全挖,要么不挖,不可以拆分,试问,想得到最多的黄金,应该选择挖取哪几个金矿。金矿信息如下表。

金矿编号 黄金储量 所需工人数目
1 400 5
2 500 5
3 200 3
4 300 4
5 350 3

题解思路

如果要使用动态规划解题,就要确定动态规划的三要素:最优子结构、边界和状态转移函数。

最优子结构

解题目标是确定10个工人挖5个金矿能够获得最多的黄金量,该问题可以从10个工人挖4个金矿的子问题中递归求解。而我们在解决10个工人挖4个金矿时,存在两种选择,一种是放弃第5个矿,将10个工人投入到挖四个矿中;另一种选择是决定对第5个矿进行挖掘,因此需要从这10个人中抽取3个人(第5个矿需要人数)加入第5个矿开采,剩余的人处理前4个矿。因此,最优解应该是这两种选择中获得黄金数量较多的那一种。

为了描述方便,设金矿数量为nnn,工人个数为www,第n个矿的黄金数量为G[n−1]G[n-1]G[n−1],第n个矿所需工人为P(n)P(n)P(n),要想获得10个矿工挖掘5个金矿的最优解为max(F(4,10),F(4,10−P[4])+G[4])max(F(4,10),F(4,10-P[4])+G[4])max(F(4,10),F(4,10−P[4])+G[4])。这就是最优子结构

边界

对于第一个金矿而言,若当前的矿工数量不能达到金矿的挖掘需要则只能放弃这个矿,获得的黄金数量为0;若能满足矿工数量要求,获得的黄金数量为G[0]G[0]G[0]。
因此,边界可以描述为

  • n=1,w>=P[0]n=1,w>=P[0]n=1,w>=P[0]时,F(n,w)=G[0]F(n,w)=G[0]F(n,w)=G[0]
  • n=1,w<P[0]n=1,w<P[0]n=1,w<P[0]时,F(n,w)=0F(n,w)=0F(n,w)=0

状态转移函数

F(n,w)={0(n<=1,w<P[0])G[0](n==1,w>=P[0])F(n−1,w)(n>1,w<P[n−1])max(F(n−1,w),F(n−1,w−P[n−1])+G[n−1])(n>1,w>=P[n−1])F(n,w) = \left\{ \begin{aligned} & 0 &(n<=1,w<P[0]) \\ & G[0] &(n==1, w>=P[0])\\ & F(n-1,w) & (n>1,w<P[n-1])\\ & max(F(n-1,w), F(n-1, w-P[n-1])+G[n-1]) &(n>1,w>=P[n-1]) \end{aligned} \right. F(n,w)=⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧​​0G[0]F(n−1,w)max(F(n−1,w),F(n−1,w−P[n−1])+G[n−1])​(n<=1,w<P[0])(n==1,w>=P[0])(n>1,w<P[n−1])(n>1,w>=P[n−1])​

到这里,三要素找到了,经过这个过程也明白了求解过程,由底向上计算,一步步推导可以得到结果,换句话说,n步的解其实就是第一步的结果推来的。

首先,我们可以使用递归的方法来写,它是自顶而下的。

def gold_mining(n ,w, G, P):if n <= 1 and w < P[0]:return 0if n == 1 and w >= P[0]:return G[0]if n > 1 and w < P[n-1]:return gold_mining(n-1, w, G, P)if n > 1 and w >= P[n-1]:return max(gold_mining(n-1, w, G, P), gold_mining(n-1, w-P[n-1], G, P) + G[n-1])if __name__ == '__main__':G = [400, 500, 200, 300, 350]P = [5, 5, 3, 4, 3]n = 5w = 10print(gold_mining(n, w, G, P))

这个解法可以求得最优解,会以树形结构求解子问题,如下图所示,不难发现,若金矿有nnn和,工人充足,复杂度即为O(2n)O(2^n)O(2n)。

优化思路

当金矿只有5个的时候,问题不是很复杂,但是当金矿数目增加的时候,这样的复杂度是无法接受的,直观上来看,这种递归之所以慢其实是因为进行了如下图所示的重复计算。


事实上,我们可以通过维护DP Table的方式来存储最优解从而以自下而上的方式求解本题,表格中的dp[i][j]就表示F(n,w)的值,因此我们可以得到如下的代码。

def gold_mining(n ,w, G, P):dp = [[0] * (w+1) for _ in range(len(G)+1)]for i in range(1, len(G)+1):for j in range(1, w+1):if j < P[i-1]:dp[i][j] = dp[i-1][j]else:dp[i][j] = max(dp[i-1][j], dp[i-1][j-P[i-1]] + G[i-1])return dp[len(G)][w]if __name__ == '__main__':G = [400, 500, 200, 300, 350]P = [5, 5, 3, 4, 3]n = 5w = 10print(gold_mining(n, w, G, P))

上述代码的时间复杂度和空间复杂度都是O(nw)O(nw)O(nw),这比递归的性能好了很多。

进一步优化

上面这段代码其实时间上已经没什么优化的空间了,空间上还可以进一步优化。我们不妨回顾DP表,可以发现,其实每一行的结果值依赖于上一行的10个结果。举个例子,我们已4个金矿9个工人为例,F(4,9)F(4, 9)F(4,9)来源于F(3,5)F(3,5)F(3,5)和F(3,9)F(3,9)F(3,9),这两个结果显然在上一行已经计算了。

因此,其实无论金矿多少个,我们也只需要保存一行的数据,在下一行计算时,从右向左统计并更新数据即可。

优化后的代码如下,可以发现,空间复杂度降低到了O(n)O(n)O(n),这就是本题的最优方法了。

def gold_mining(n ,w, G, P):dp = [0] * (w+1)for i in range(1, len(G)+1):for j in range(w, 0, -1):if j >= P[i-1]:dp[j] = max(dp[j], dp[j-P[i-1]] + G[i-1])return dp[w]if __name__ == '__main__':G = [400, 500, 200, 300, 350]P = [5, 5, 3, 4, 3]n = 5w = 10print(gold_mining(n, w, G, P))

补充说明

具体代码可以查看我的Github,欢迎Star或者Fork,参考书《你也能看得懂的Python算法书》,书中略微有一点不合理之处,做了修改。到这里,其实你已经体会到了动态规划的简约之美,当然,要注意Python是有递归深度限制的,如不是必要,建议使用循环控制。

动态规划算法-02矿工挖矿问题相关推荐

  1. 实验二 动态规划算法 最大字段和问题

    基本题二:最大字段和问题 一.实验目的与要求 1.熟悉最长最大字段和问题的算法: 2.进一步掌握动态规划算法: 二.实验题 若给定n个整数组成的序列a1,a2,a3,...,an,求该序列形如ai+a ...

  2. 漫画说算法--动态规划算法三(绝对通俗易懂,非常棒)

    在前两集漫画中,我们通过一个算法问题的完整解题过程,讲述了动态规划的基本概念和思想.没看过前两集的朋友可以点击下面的链接: 漫画说算法–动态规划算法一(绝对通俗易懂,非常棒) 漫画说算法–动态规划算法 ...

  3. 【算法设计与分析】流水作业调度问题 动态规划算法与代码实现 超详细

    题目描述 "加工顺序问题"又被称为"批处理作业调度问题". 设有n个工件需要在机器M1和M2上加工,每个工件的加工顺序都是先在M1上加工,然后在M2上加工.t1 ...

  4. 动态规划算法据序偶原理求解0/1背包问题(C++实现)

    可程序根据序偶原理,应用动态规划算法求解. Code   1//说明:本程序有一定代码冗余,若分割为多个函数的形式会使程序简洁明了.   2#include <iostream>   3u ...

  5. 零起点学算法02——输出简单的句子

    零起点学算法02--输出简单的句子 Description 会输出Hello World!了,那换个句子也会吧? Input 没有输入 Output 现在要求你输出下面红色的字  Nice to me ...

  6. 矩阵连乘算法代码JAVA_矩阵连乘问题的动态规划算法(java)

    /** * 下面是矩阵连乘问题的动态规划算法 * 假设有6个矩阵: * A1 A2A3 A4 A5A6 * 30*35 35*15 15*5 5*10 10*20 20*25 则matrixChain ...

  7. 五大常用算法之二:动态规划算法

    基本概念 动态规划过程是:每次决策依赖于当前状态,又随即引起状态的转移.一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划. 基本思想与策略 基本思想与分 ...

  8. 矩阵连乘问题(动态规划算法)

    动态规划算法思想简介: 将一个问题分解为多个子问题,这点和分治法类似,但是每个子问题不是独立的而是相互联系的,所以我们在求解每个子问题的时候可能需要重复计算到其他的子问题,所以我们将计算过的子问题的解 ...

  9. 【字符串】最长回文子串 ( 动态规划算法 ) ★

    文章目录 一.回文串.子串.子序列 二.最长回文子串 1.动态规划算法 2.动态规划算法代码示例 一.回文串.子串.子序列 " 回文串 ( Palindrome ) " 是 正反都 ...

最新文章

  1. CodeForces - 261B Maxim and Restaurant
  2. 设计模式 之 --- GRASP
  3. 理解Linux系统中的load average
  4. oracle 修改sgamaxsize_oracle初始化内存配置参数(sga,pga,sharepool)
  5. 文章马伊琍离婚,华为却成“最大赢家”?
  6. 关于曼哈顿距离和切比雪夫距离的转换和应用
  7. 如何把jpg转换成word文档
  8. pta 计算圆周率(C语言实现)
  9. 记录-使用TM1640 数码管驱动芯片
  10. 南天PR2 PR2E PR9 中航 PR-b PR-V PR-C PR-t PR-u PR-d GWI PR2 plus 打印机驱动安装视频教程
  11. 雷达(微波)感应天线设计
  12. kafka 报错 SyntaxError: invalid syntax
  13. Oracle 12c之Oracle 12c与云计算
  14. Fortran学习3:控制流2:循环
  15. Ping 命令详解(含真实操作截图)
  16. linux 文件夹 775,linux - 如何为文件夹及其所有子文件夹和文件设置chmod?
  17. 大象做梦传媒写2022年公司大型年会主持稿完整版
  18. netty-Android-RN等博文收藏
  19. Power BI----M函数*
  20. 联想G470更换SSD

热门文章

  1. 手写自己的MyBatis框架-语句执行处理
  2. MyBatis 源码解读-databaseIdProviderElement()
  3. 客户端支持的cookie
  4. MIME Type描述消息内容类型的因特网标准
  5. 类文件结构-访问标识和继承信息
  6. redis常见应用场景
  7. maven工程运行环境修改
  8. 享元模式源码解析(jdk+tomcat)
  9. oracle最大空闲时间,使用Oracle PROFILE控制会话空闲时间
  10. Maven(1)--坐标与依赖