动态规划是将问题分解成若干个子问题,依次求解子问题,且前一个子问题的解为后一个子问题的求解提供信息。最后一个子问题的解即为原问题的解。
动态规划中的每个子问题只求解一次,一旦子问题的解被求出,则将该解存储起来,方便之后的子问题求解。相比递归算法,动态规划中每个子问题只求解一次,具有天然的剪枝功能,大大减少了计算量与时间。
我们要针对问题,设计状态转移方程,寻找最优子结构和边界。这是实现动态规划的关键。
问题描述:
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)只被计算过一次,并存储下来,这就是动态规划求解的过程。
在程序中,我同时用动态规划与递归算法对问题进行求解,并记录求解需要的时间。对比发现,动态规划求解问题的明显比递归算法求解高效太多了。
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级,问走到顶点有多少种走法?
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。
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元,问最少需要多少个硬币?
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列的位置,问共有多少条路径?
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];
}
}