动态规划(多个例子说明以及java实现)

基本思想

动态规划是将问题分解成若干个子问题,依次求解子问题,且前一个子问题的解为后一个子问题的求解提供信息。最后一个子问题的解即为原问题的解。

动态规划中的每个子问题只求解一次,一旦子问题的解被求出,则将该解存储起来,方便之后的子问题求解。相比递归算法,动态规划中每个子问题只求解一次,具有天然的剪枝功能,大大减少了计算量与时间。

动态规划三要素

  1. 状态转移方程
  2. 最优子结构
  3. 边界

我们要针对问题,设计状态转移方程,寻找最优子结构和边界。这是实现动态规划的关键。

例子

国王与金矿

问题描述:
10个工人挖5座金矿,每个金矿含有的黄金量和需要的人数为:500斤/5人、400斤/5人、350斤/3人、300斤/4人、200斤/3人。且要求每个金矿要么全挖,要么不挖,问怎么分配工作能尽可能多地挖出黄金?

设置参数:
假设挖出的黄金总量为 F(n,w),n为开挖的金矿数量,w为工人数量。g[n]={500,400,350,300,200}表示第n个金矿能够挖出的黄金数量,p[n]={5,5,3,4,3}表示开挖第n个金矿需要的工人数量。

最优子结构

首先构建第一个子问题,我们的人数 w=10 能够挖5座金矿吗?这时候,挖出的黄金数量就存在两种情况:

能挖5座金矿:F(5,w)=F(4,w-p[4])+g[4]
不能挖5座金矿:F(5,w)=F(4,w)

以上两个便是第一个子问题的两个最优子结构。

状态转移方程

由第一个子问题的最优子结构,我们可以推导出其他子问题的最优子结构,即状态转移方程:

F(n,w)=F(n-1,w-p[n-1])+g[n-1]
F(n,w)=F(n-1,w)

假如工人数量能开挖n座金矿(能获得黄金数量F(n-1,w-p[n-1])+g[n-1]),也能开挖n-1座金矿(能获得黄金数量F(n,w)=F(n-1,w))。为了尽可能地获取黄金,F(n,w)要取其中的最大值:

F(n,w) = max {F(n-1,w), F(n-1,w-p[n-1])+g[n-1]}

注意,前面的n座金矿挖出的黄金量不一定比后面的n-1座金矿挖出的黄金量多。举个例子,我们有10个工人,现在有两种情况,我们能挖500斤/5人和400斤/5人的2座金矿,也能挖350斤/3人、300斤/4人、200斤/3人的3座金矿。为了尽可能多的挖出黄金,我们必然让10个人去2座金矿(共900斤)。

边界

该问题的边界为:只有一座金矿。而这座金矿挖与不挖取决于我们的人数w是否足够:

若 w>=p[0],F(1,w)=g[0]
若 w<p[0],F(1,w)=0

求解过程

首先,我们有10个工人。
如果挖1座金矿,我们就要找出能找出满足该条件的矿的组合,显然5座矿都满足,我们将挖出最多的黄金记录下来,即F(1,10)=500;
如果挖2座金矿,任意两两金矿组合均满足条件,记录最多的黄金数量,并与F(1,10)=500比较取最大值,结果为第1、2金矿的总黄金量最多,即F(2,10)=900;
如果挖3座金矿,仅有挖第3、4、5座金矿符合,共有黄金850斤,然而与F(2,10)相比少了,故F(3,10)=900;
显然,10个人不可能挖4或者5座金矿,故F(4,10)=F(5,10)=900。
求解过程中,F(n,10)只被计算过一次,并存储下来,这就是动态规划求解的过程。

java代码

在程序中,我同时用动态规划与递归算法对问题进行求解,并记录求解需要的时间。对比发现,动态规划求解问题的明显比递归算法求解高效太多了。

public class King_Gold {
   //动态规划用时的确比递归算法少
	public static void main(String[] args) {
   
		int[] gold = {
   400, 500, 200, 300, 350};
        int[] people = {
   5, 5, 3, 4, 3};
		
        long startTime_1 = System.nanoTime();
        System.out.println(getMostGold_1(5, 10, gold, people));
        long endTime_1 = System.nanoTime();
		System.out.println("递归算法(用时):"+(endTime_1 - startTime_1)+"ns");
		
		long startTime_2 = System.nanoTime();
        System.out.println(getMostGold_2(5, 10, gold, people));
        long endTime_2 = System.nanoTime();
		System.out.println("动态规划(用时):"+(endTime_2 - startTime_2)+"ns");
    }
	
    public static int getMostGold_1(int n, int worker, int[] g, int[] p) {
   //递归算法
        if(n==1&&worker<p[0])
            return 0;
        if(n==1&&worker>=p[0])
            return g[0];
        if(n>1&&worker<p[n - 1])
            return getMostGold_1(n - 1, worker, g, p);
        return Math.max(getMostGold_1(n - 1, worker, g, p), (getMostGold_1(n - 1, worker - p[n - 1], g, p) + g[n - 1]));
    
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值