动态规划:国王与金矿

题目解析

有一个国家发现了5座金矿,每座金矿的黄金储量不同,需要参与挖掘的工人数也不同。参与挖矿工人的总数是10人。每座金矿要么全挖,要么不挖,不能派出一半人挖取一半金矿。要求用程序求解出,要想得到尽可能多的黄金,应该选择挖取哪几座金矿?

第一座金矿含金500,需要5人;第二座金矿含金200,需要3人;第三座金矿含金300,需要4人;第四座金矿含金350,需要3人;第五座金矿含金400,需要5人。

排列组合

每个金矿要么挖要么不挖,共有2^5组合方法。通过枚举这些组合,选出最大产出的组合。组合方式可用一棵树示意。
在这里插入图片描述
自顶向下进行遍历分支(从根部到叶子的路径为一种组合情况),算出分支的人力和产出,得出最大的产出那种组合。

动态规划

问题建模

动态规划有三个核心元素:最优子结构、边界、状态转移方程。

  • 问题一:这个问题的最优子结构:

    • 如果我们剩下最后一个也就是第5个金矿要考虑了,这时候会出现几种情况呢?
      • 没有足够的人手,因此一定不能挖。因此答案是:挖前4个金矿,10个工人的最多金子数量,即(前4个金矿10工人的挖金数量)
      • 有足够的人手,但是可以选择挖或者不挖
        • 不挖,因此答案是:挖前4个金矿,10个工人的最多金子数量,即(前4个金矿10工人的挖金数量)
        • 选择挖,因此答案是:第5个金矿的挖金数量 + 挖前4个金矿,10-3工人的最多金子数量。
    • 因此,问题的最优子结构有两个:
      • 当所剩工人不够挖掘当前金矿时,只有一种最优子结构:
        • 挖前4金矿,10工人的最优选择
      • 当所剩工人足够挖掘当前金矿时,有两种最优子结构
        • 一个是4金矿10工人时的最优选择
        • 一个是4金矿10-3工人时的最优选择 (第5个金矿要用掉3个工人数)
  • 问题二:推导状态转移方程。要推导状态转移方程,就是要推导最优子结构和最终问题有什么关系呢?也就是说,4个金矿的最优选择和5个金矿的最优选择之间,是什么样的关系?

    • 关系:
      • 当人手不够挖第5个金矿时,答案肯定是: (前4个金矿10工人的挖金数量)
      • 当人手足够挖第5个金矿时,这时有两种子结构,需要考虑这两种子结构和第5做金矿的关系。不用想,选择收益最大的,也就是
      • 5个金矿的最优选择 ,就是(前4个金矿10工人的挖金数量) 和(前4个金矿7工人的挖金数量 + 第5座金矿的挖金数量)之间的最大值
    • 从而可以推导出状态转移方程。
      • 这里有三个变化的东西:第N个金矿,还有多少工人数,可以得到的最多金子数量。最多金子数量是目标,因此它应该是返回值,所以我们需要考虑两个变化维度,用数学语言来想,就是要求一个二维函数,假设为F(n, w),n为第i座金矿,w为当前所剩工人数量。
      • 我们把金矿数量设为N,工人数设为W,金矿的黄金量设为G[]、金矿的用工量设为P[]
      • 因此5金矿和4金矿的最优选择之间存在这样的关系:F(5, 10) = MAX(F(4, 10), F(4, 10 - P[4]) + G[4])
  • 问题三:这个问题的边界

    • 当金矿数为0或者工人数为0时,F(n, w) = 0
  • 问题的状态转移方程为

    • 设金矿数量是n,工人数量是w,金矿的含金量为数组g[],金矿所需开采人数设为数组p[],设F(n, w)为n个金矿,w个工人时的最佳收益(返回值为最佳收益),那么状态转移方程是:
      • 问题边界:金矿数为0或者工人数为0的情况:F(n, w) = 0(n = 0或者w = 0)
      • 当所剩工人不够挖当前金矿时,只有一种最优子结构F(n,w)=F(n-1,w),(n>=1,w<p[n-1])
      • 常规情况下有两种最优子结构(即挖当前金矿和不挖当前金矿):F(n,w)=max(F(n-1,w),F(n-1,w-p[n-1]+g[n-1])),(n>=1,w>=p[n-1])

简单来说,可以这么看

  • 当人数不足以挖金矿时,只能获得 0 黄金
  • 当人数只能够挖一座金矿时,选择最大黄金数的金矿
  • 当人数足够挖两个金矿时,选择是挖一座黄金量多的黄金,还是挖两座黄金量少的黄金

题目解析

/**
     * 获得金矿最优权益
     * @param w 工人数量
     * @param n 可选金矿数量
     * @param p 金矿开采所需的工人数量
     * @param g 金矿储量
     * @return  最优收益
     */
    public static int getBestGoldMining(int w,int n,int[] p,int[] g){
        if(w==0||n==0){
            return 0;
        }
        if (w<p[n-1]){
            return getBestGoldMining(w,n-1,p,g);
        }
        return Math.max(getBestGoldMining(w,n-1,p,g),
                getBestGoldMining(w-p[n-1],n-1,p,g)+g[n-1]);
    }

/**
     * 获得金矿最优权益
     * @param w 工人数量
     * @param p 金矿开采所需的工人数量
     * @param g 金矿储量
     * @return  最优收益
     */
    public static int getBestGoldMining(int w,int[] p,int[] g){
        //创建表格
        int[][] resultTable=new int[g.length+1][w+1];
        //填充表格
        for (int i = 1; i <=g.length; i++) {
            for (int j = 1; j <=w; j++) {
                if (j<p[i-1]){
                    resultTable[i][j]=resultTable[i-1][j];
                }else {
                    resultTable[i][j]=Math.max(resultTable[i-1][j],resultTable[i-1][j-p[i-1]]+g[i-1]);
                }
            }
        }
        //返回最后一个格子的值
        return resultTable[g.length][w];
    }

还可以进行空间上的优化,因为二维数组的每一行的结果都可以由上一行推导得出,因此我们不需要保存整张表格,只保存一行即可,在计算下一行时,进行覆盖替换即可。

/**
     * 获得金矿最优权益
     * @param w 工人数量
     * @param p 金矿开采所需的工人数量
     * @param g 金矿储量
     * @return  最优收益
     */
    public static int getBestGoldMiningV3(int w,int[] p,int[] g){
        //创建当前结果
        int[] results=new int[w+1];
        //填充一维数组
        for (int i = 1; i <g.length; i++) {
            for (int j=w;j>=1;j--){
                if (j>=p[i-1]){
                    results[j]=Math.max(results[j],results[j-p[i-1]]+g[i-1]);
                }
            }
        }
        //返回最后一个格子的值
        return results[w];
    }

动态规划是怎么填表

在这里插入图片描述

  • 表格的第一列表示给定前1-5个金矿的情况,也就是N的取值。
  • 表格的第一行表示给定的工人数,也就是W的取值
  • 表格的其余的空白格,表示给定N和W值对应的黄金获得数,也就是F(N, w)

下面我们来填表:

  • 第一个金矿有400金,需要5个工人。所以前4个格子都是0,因为人数不够。后面的格子都是400,因为只有这一个金矿可以挖
    在这里插入图片描述
  • 第二个金矿有500金,需要5个工人。那么第二行的前四个格子怎么计算呢?因为W<5,也就是人数不够,所以F(N, W) = F(N - 1, W) = 0
    在这里插入图片描述
  • 第二行的后6个格子怎么计算呢?因为W>=5,人数足够了,所以F(N, W) = MAX(F(N - 1, W),F(N - 1,W - 5)+ 500)。所以第五个格子的值是500
    在这里插入图片描述
  • 需要注意的是第2行的第10个格子,也就是N=2,W=10的时候,F(N - 1, W) = 400,F(N - 1,W-5)=400,MAX(400,400+500)=900
    在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

这是一个典型的0-1背包问题,工人总数可以看为背包的容量,金矿的个数可以看为物品的个数,金矿的含金量可以看作物品的价值,金矿的使用工人数可以看作物品所占空间数,这样一来就变成了0-1背包问题.

我们知道,动态规划方法适合的题型的4个基本特点是:

  • 最优子结构:当前一个状态得到最佳解时,当前状态在前一个状态下一定有最佳解
  • 子问题重叠:每个状态下要解决的问题除了参数不同之外,其本质是一样的
  • 有边界:当解决了最后一个子问题时,整个问题得解
  • 子问题独立:解决一个子问题时不依赖于另一个同级的子问题,只与它的母问题有关

当存在这四个特点时很大程度上可以确定用动态规划的方法解决了。

而解决动态规划问题的关键在于写出状态方程。一般来说,对应一个状态下,对某件事情是否执行,这是两个子问题,每个子问题都可以递归到下一个状态,最终到达边界条件返回,再判断最开始状态下两个子问题的最优解,就是整个问题的答案。

这里使用国王的金矿的例子来解释动态规划的实现过程:

有一个国家的国王为了增强国力要开采已探明储量的5座金矿,开采每一座金矿所需的人员是固定的,而且为了能顺利将金矿开采又不耽误人民生活,国王决定只调配500人去挖金矿,要同时开采所有金矿,而且每个人民只开采一次,他要向国会说明开采金矿能带来多少金子,但是问题来了,由于没有足够的人手一次性把所有金矿都开采,怎么搞清能获得最多金子的数量是个难题。

国王是这样处理的:

  • 他对左丞相说我们不开采第5座金矿,你告诉我开采前4座金矿最多能获得多少金子,
  • 又对右丞相说我要开采第5座金矿,用掉100个劳力,你想办法告诉我开采前4座金矿最多能获得多少金子。
  • 然后在这两个答案中选择一个最佳的

左丞相也叫来两个大臣

  • 对其中一个说,我要240人用于开采第4、5座金矿,其它人手给你调配的话,你告诉我前三座金矿最多能开采多少金子
  • 又对另一个说我要用100人开采第5座金矿,第4座不开采,其它人手给你调配的话你告诉我前三座金矿最多能挖多少金子。

右丞相,也决定学国王的做法,把前几个金矿的最大开采量交给属下去解决,只决定一个金矿是否开采得出最大值。于是,他也找来两个大臣,让它们分别在开采和不开采第4个进程的前提下调查前三个金矿的最大收益。

从上面我们可以看出:

  • 知道第i-1个金矿的最大收益就一定是知道第i个金矿的最大收益,这就是最优子结构
  • 每个人要知道i个金矿的最大收益,就必须知道前i-1个金矿的最大收益,这就是子问题重叠
  • 最终当当考虑第1座金矿的最大产量时,只要看是否有足够人手开采第1座金矿,有的话,答案是已探明的储量,没有的话就是0,然后答案汇报到上级,上级再得出第2座金矿开采与不开采得出的较大产量,再往上汇报…,这就是边界
  • 每个人从上级得到的前提都是不同的,上级决定开不开采,再将这个前提之一告诉下属,而下属不需要考虑上级给另一个下属什么前提,这就是子问题独立

总结:

  • 子问题:国王需要根据两个大臣的答案以及最后一个金矿的信息才能判断出最多能开采出多少金子,为了解决自己的问题,它需要给别人制造另外两个问题,这两个问题就是子问题
  • 最优子结构:国王相信,只要它的两个大臣能够回答出正确的答案,再加上他聪明的判断就一定能得到最终的正确答案。我们把这种子问题最优时母问题通过优化选择后一定最优的情况叫做最优子结构
  • 子问题重叠:实际上国王也好,大臣也好,所有人面对的都是同样的问题,即给你一定数量的人,给你一定数量的金矿,让你求出能够开采出来的最多金子数。我们把这种母问题和子问题本质上是同一个问题的情况叫做子问题重叠。然而问题中出现的不同点就是被子问题之间传递的参数,比如这里的人数和金矿树
  • 边界:子问题在一定时候就不需要提出子子问题的情况叫做边界,如果没有边界就会出现死循环
  • 子问题独立:要知道,当国王的两个大臣在思考它们自己的子问题时他们是不会关心对方是如何计算怎样开采金矿的,因为他们知道,国王只会选择两个人中的一个作为最后方案,另一个人的方案并不会被采用,因此一个人的决定对另一个人的决定是没有影响的。我们把这种一个母问题在对子问题选择时,当前被选择的子问题两两互不影响的情况叫做“子问题独立”
  • 备忘录:当我们遇到相同的问题时,我们可以问同一个人。讲的通俗一点就是,我们可以把问题的解放在一个变量中,如果再次遇到这个问题就直接从变量中获得答案,因此每一个问题仅会计算一遍,如果不做备忘的话,动态规划就没有任何优势可言了。

总结:

  • 我们必须要明确的是,动态规划它不是算法,它只是一种方法,它是在一件事情发生的过程中寻找最优解的方法。因此,我们需要对这件事情所发生的过程进行考虑。而通常我们从过程的最后一步开始考虑,而不是先考虑过程的开始
  • 而过程的开始,也就是考虑的最后一步,就是边界。
  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要挖掘金矿html源代码,首先需要了解HTML是什么。HTML(HyperText Markup Language)是一种用于创建网页的标准标记语言,它使用标签和元素来描述和组织网页的结构和内容。 挖掘金矿html源代码的过程主要分为三个步骤:分析、提取和利用。 首先,在分析阶段,需要打开目标网页,并通过查看网页源代码来获取到HTML源代码。可以按下键盘上的Ctrl+U组合键,在浏览器中打开网页源代码,也可以在浏览器中使用开发者工具(一般是F12键)来查看源代码。复制源代码并保存。 接下来,在提取阶段,需要从HTML源代码中提取出有价值的信息。可以使用文本编辑器(如记事本)或专业的HTML编辑器(如Visual Studio Code)打开保存的源代码文件。通过观察和分析代码结构,识别出需要的数据,如网页标题、文本内容、链接地址等,并提取出来。 最后,在利用阶段,可以根据提取的信息进行进一步的操作。根据需求,可以在提取的文本内容中进行搜索或分析,找到目标关键词、统计词频等。或者根据提取的链接地址进行访问,爬取更多的网页信息。 需要注意的是,在进行这个过程时,应该遵守相关法律和道德规范,不应该非法获取或滥用他人的网页数据。 总之,挖掘金矿html源代码是一个技术活,需要有一定的HTML语言基础和相关工具的运用能力。希望我的回答能够帮助到你。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值