动态规划:旷工挖矿(递归和非递归实现)——02

矿工挖矿问题

  • 该题有递归和非递归的实现,这里两种实现思路都给出来了

问题描述

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

算法思路

  • 首先动态规划的三要素(最优子结构,边界,状态转移方程)中我们最先需要找到的就是状态转移方程,而状态转移方程又会涉及到子问题的最优结构,所以我们先看看子问题是什么?

  • 在这里首先10个旷工是不会变的,无论挖几个矿都是这10个旷工,所以我们要去减少问题规模的话就是减少矿的个数。所以子问题为:10个旷工挖前 X 个矿最多可以赚多少钱! 实际上后面发现子问题还要被扩展为 w个旷工挖前n个矿最大钱数

  • 在动态规划的算法设计中,我们往往不能够从第一步去开始分析,而是从最后一步开始分析!在这里就是分析已经计算完了10个旷工挖前4个矿最多赚多少钱,如何去计算挖前5个矿的最大钱数。而此时我们会面临两个抉择:

    • 这里先设F(n,w)为 w 个工人挖 n 个矿的最大金钱数,p(i)为挖第 i 个矿需要的矿工人数,G(i)为第 i 个矿中的黄金数量

    • 根据动态规划的算法,假设我们现在已经计算出了F(4,10),如何计算F(5,10),其实思路很简单,要不然就挖第五个矿,要不然就不挖第五个矿

    • 如果不挖第五个矿:那么意思就是这十个人就挖前四个,计算出最大钱数

      M1 = F(4,10)

    • 如果挖第五个矿: 那就要将十个人分出3个人挖第五个矿,剩下的七个人挖前四个矿,那么此时计算出的最大钱数为:
      M2 = F(4, 7)+ G(5)

    • 最终我们应该取M1和M2的最大值,即Max(M1,M2) ,推广到一般式就是

      F(n,w) = max(F(n-1,w),F(n-1,w - p(i))+ G(i))

  • 根据以上思路我们就可以得推广到一般情况,得出动态规划的三要素:

    • 最优子结构:就是上面的F(n - 1,w),F(n -1,w-p(i))

    • 边界:当只挖一个矿的时候F(n,w)

      • n == 1时,w >= P【1】,F(n,w) = G【1】
      • n == 1时,w < P【1】,F(n,w) = 0;
    • 状态转移函数:

      • n == 1时,w >= P【1】,F(n,w) = G【1】
      • n == 1时,w < P【1】,F(n,w) = 0;
      • n > 1时,w < P【n】,F(n,w) = F(n-1,w)
      • n > 1时,w >= P【n】,F(n,w) = max(F(n-1,w),F(n-1,w - p(i))+ G(i))
  • 这里稍微解释一下上面状态转移函数的第三种情况,例如我有一个这种情况F(2,4),四个人挖前两个矿,但是第二个矿至少要五个人,那么就不可能挖第二个矿,所以只能回归到子问题,F(1,4)。

  • 以上来看动态规划的三要素都已经出来了,那么接下来就是实现了!

递归实现

	

	public static int[] money = new int[]{400,500,200,300,350};

    public static int[] workernums = new int[]{5,5,3,4,5};


    /**
     *
     * @param w  几个工人
     * @param n  挖几个矿
     * @return
     */
    public static int caculateMaxMoney(int w, int n){


        if(n == 1 && w < workernums[0]) {
            return  0;
        }
        if(n == 1 && w >= workernums[0]) {
            return money[0];
        }

        //人数不够挖新的矿了
        if(n > 1 && w < workernums[n - 1]){

            return caculateMaxMoney(w, n-1);
        }

        if(n > 1 && w >= workernums[n - 1]){

            int M1 = caculateMaxMoney(w - workernums[n - 1], n - 1) + money[n - 1];

            int M2 = caculateMaxMoney(w,n-1);

            return Math.max(M1,M2);
        }

        throw new IllegalArgumentException("no result!");

    }

非递归实现

  • 对于非递归实现,就需要设计一个备忘录,也就是一个dp矩阵,去存储子问题的计算结果
  • 这里定义dp【6】【11】,dp【i】【j】表示 j 个矿工挖前 i 个金矿的最大价值
  • 但是需要注意一点,我们需要对其进行初始化,特别是对i = 1行进行初始化,初始化后dp矩阵如下图,这里的初始化也就是上述状态方程中n == 1的前两个状态方程的计算。

在这里插入图片描述

  • 之后开始两层遍历每一个dp【i】【j】,根据两个状态方程进行判断计算
	public static int[] money = new int[]{400,500,200,300,350};

    public static int[] workernums = new int[]{5,5,3,4,5};

public static int caculateMaxMoney1(){
        int[][] dp = new int[6][11];
        //dp矩阵初始化
        for(int j = 1; j < 11; j++ ){
            if(j >= workernums[0]) dp[1][j] = money[0];
            else dp[1][j] = 0;
        }

        for(int i = 2; i < 6; i++){
            for(int j = 1; j < 11; j++){
                if(j < workernums[i - 1]) dp[i][j] = dp[i-1][j];
                else {
                    dp[i][j] = Math.max(dp[i-1][j],dp[i - 1][j - workernums[i - 1]] + money[i - 1]);
                }
            }
        }


        return dp[5][10];

    }

最后的dp矩阵如下:
在这里插入图片描述

总结:

  • 对于动态规划的思路解法,往往不能从第一步开始看,而是要从最后一步开始看,是否可以找到子问题和状态转移方程
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值