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

动态规划

  • 基本思想
  • 动态规划三要素
  • 例子
    • 国王与金矿
      • 最优子结构
      • 状态转移方程
      • 边界
      • 求解过程
      • java代码
    • 上阶梯问题
    • java代码
    • 数字三角形问题
    • java代码
    • 凑零钱问题
      • java代码
    • 不同路径问题
      • 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

求解过程

首先,我们有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]));
    }
    
    public static int getMostGold_2(int n,int w,int []g,int[] p){//动态规划
        int[] F = new int[w + 1];
        for (int i=n;i>0;i--) 
            for (int j = w; j > 0; j--) 
                if (j>=p[i - 1]) 
                    F[j] = Math.max(F[j], F[j - p[i - 1]] + g[i - 1]);
        return F[w];
	}
}

上阶梯问题

问题描述:
有10个阶梯,每次只能往上走1级或者2级,问走到顶点有多少种走法?

java代码

	public static void main(String[] args) {
		long startTime_1 = System.nanoTime();
		System.out.print(UpStep_1(10));
		long endTime_1 = System.nanoTime();
		System.out.println("\n用时:"+(endTime_1 - startTime_1)+"ns");
		
		long startTime_2 = System.nanoTime();
		System.out.print(UpStep_1(10));
		long endTime_2 = System.nanoTime();
		System.out.println("\n用时:"+(endTime_2 - startTime_2)+"ns");
	}
	
	public static int UpStep_1(int n) {//递归
		if(n==1)
			return 1;
		if(n==2)
			return 2;
		return UpStep_1(n-1)+UpStep_1(n-2);
	}
	
	public static int UpStep_2(int n) {//动态规划
		int d[]=new int [n+1];
		d[1]=1;
		for(int i=2;i<=n;i++) {
			d[i]=d[i-1]+d[i-2];
		}
		return d[n];
	}
}

数字三角形问题

问题描述:
从顶部往底部走,只允许向左下或者右下方走,问所有路径中数字的最大和是多少?

      7
    4   5
  1   4   9
2   3   4   5

以该数字三角形为例,显然一直往右下方走的和是最大的:7+5+9+5=26。

java代码

	public class Triangle {
	public static void main(String args[]) {
		int D[][]= {{7}, {4, 5}, {1, 4, 9}, {2, 3, 4, 5}, {1,2,3,4,5}};
		long startTime_1 = System.nanoTime();
		System.out.print(Triangle_1(D,0,0));
		long endTime_1 = System.nanoTime();
		System.out.println("\n用时:"+(endTime_1 - startTime_1)+"ns");
		
		long startTime_2 = System.nanoTime();
		System.out.print(Triangle_2(D));
		long endTime_2 = System.nanoTime();
		System.out.println("\n用时:"+(endTime_2 - startTime_2)+"ns");
	}
	
	public static int Triangle_1(int D[][], int i, int j) {
		if(i==D.length-1)
			return D[i][j];
		return Math.max(Triangle_1(D, i+1, j), Triangle_1(D, i+1, j+1))+D[i][j];
	}
	
	public static int Triangle_2(int D[][]) {
		int n=D[D.length-1].length;
		int sum[][]=new int [n+1][n+1];
		for(int i=n-1;i>=0;i--) 
			for(int j=D[i].length-1;j>=0;j--)
				sum[i][j]=Math.max(sum[i+1][j], sum[i+1][j+1])+D[i][j];
		return sum[0][0];
	}
}

凑零钱问题

问题描述:
我有1、2、5元的硬币,并且想用这些硬币凑够11元,问最少需要多少个硬币?

java代码

public class LeastCoins {
	public static void main(String args[]) {
		int coin[]= {1, 2, 5};
		int money=20;
		long startTime_1 = System.nanoTime();
		System.out.print(LeastCoins_1(coin, money));
		long endTime_1 = System.nanoTime();
		System.out.println("\n用时:"+(endTime_1 - startTime_1)+"ns");
		
		long startTime_2 = System.nanoTime();
		System.out.print(LeastCoins_2(coin, money));
		long endTime_2 = System.nanoTime();
		System.out.println("\n用时:"+(endTime_2 - startTime_2)+"ns");
	}

	public static int min(int a, int b, int c) {
		int min=a;
		if(a>b)
			min=b;
		if(c<min)
			min=c;
		return min;
	}
	
	public static int LeastCoins_1(int coin[], int amount){
		if(amount==1)
			return 1;
		if(amount==2)
			return 1;
		if(amount==5)
			return 1;
		if(amount<5&&amount>1)
			return 1+Math.min(LeastCoins_1(coin, amount-coin[0]), LeastCoins_1(coin, amount-coin[1]));
		return 1+min(LeastCoins_1(coin, amount-coin[0]), LeastCoins_1(coin, amount-coin[1]), LeastCoins_1(coin, amount-coin[2]));
	}
	
	public static int LeastCoins_2(int coin[], int money) {
		int amount[]=new int[money+1];
		for(int i=0;i<=money;i++)//初始化money由1块硬币组成
			amount[i]=i;
		for(int i=1;i<=money;i++) 
			for(int j=0;j<coin.length;j++) 
				if(i>=coin[j])
					amount[i]=Math.min(amount[i], amount[i-coin[j]]+1);
		return amount[money];
	}
}

不同路径问题

问题描述:
在一个m*n的棋盘中,我处于第1行1列的位置,且每次我只能往下或者往右走,直到我能走到m行n列的位置,问共有多少条路径?

java代码

 public class DifferentPath {
	public static void main(String args[]) {
		long startTime_1 = System.nanoTime();
		System.out.print(DifferentPath_1(4, 5));
		long endTime_1 = System.nanoTime();
		System.out.println("\n用时:"+(endTime_1 - startTime_1)+"ns");
		
		long startTime_2 = System.nanoTime();
		System.out.print(DifferentPath_2(4, 5));
		long endTime_2 = System.nanoTime();
		System.out.println("\n用时:"+(endTime_2 - startTime_2)+"ns");
	}
	
	public static int DifferentPath_1(int i, int j) {
		if(i==1&&j==1)
			return 1;
		if(i==1&&j>1)
			return DifferentPath_1(i, j-1);
		if(j==1&&i>1)
			return DifferentPath_1(i-1, j);
		return DifferentPath_1(i-1, j)+DifferentPath_1(i, j-1);
	}
	
	public static int DifferentPath_2(int h, int l) {
		int d[][]=new int[h][l];
		for(int i=0;i<h;i++)
			for(int j=0;j<l;j++)
				d[i][j]=1;
		for(int i=1;i<h;i++) 
			for(int j=1;j<l;j++) 
				d[i][j]=d[i-1][j]+d[i][j-1];
		return d[h-1][l-1];
	}
}

你可能感兴趣的:(动态规划)