一学期前曾经学习过一点动态规划,但是云里雾里,感觉玄乎其玄。不知所以然就把无解的问题解决了,很是牛逼。
今天有幸看到了一篇博文。教你彻底学会动态规划——入门篇http://blog.csdn.net/baidu_28312631/article/details/47418773。实在是受益匪浅。由此打开新世界的大门,记录下我自己的理解。
首先引入一个问题如下,数字三角形(POJ1163)
在上面的数字三角形中寻找一条从顶部到底边的路径,使得路径上所经过的数字之和最大。路径上的每一步都只能往左下或 右下走。只需要求出这个最大和即可,不必给出具体路径。 三角形的行数大于1小于等于100,数字为 0 - 99
这道题讲的有点复杂,我们可以这么看,
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
从顶点开始往下走,只可以向下或者向右下方走。把经过的路径上的数字累加起来,求出最大值。
为了简便说明,我们使用 matrix[i,j] 代表第 i 行 第 j 个元素,使用 maxSum[i,j]表示从位置 matrix[i,j] 到终点的路径值,那么题目就是求 maxSum[0,0]。
经过某一个位置时 matrix[i,j] 时,可以经过 matrix[i+1,j] 或者 matrix[i+1,j+1].
我们试着想一下递推公式
注意经过最后一行时 maxSum[i,j] 就是最后一行元素的值。
if(i==n-1)
maxSum[i,j]=matrix[i,j];
else
maxSum[i,j]=matrix[i,j]+max{maxSum[i+1,j],maxSum[i+1,j+1]};
所以我们可以敲下代码
/**
* 获取路径最大值
*/
private void calculate(){
int max=getSumMax(0,0);
System.out.println("从顶到底路径最大值为:"+max);
}
private int getSumMax(int i,int j){
if(i==lineCount-1)
return inputData[i][j];
int x=getSumMax(i+1,j);
int y=getSumMax(i+1,j+1);
return Math.max(x, y)+inputData[i][j];
}
给出完整代码
package com.luo.dongtaiguihua;
import java.util.Scanner;
import com.luo.util.SortUtils;
/*
* 初识动态规划
* 效率底下,即递归又不备忘
* */
public class DongTaiGuiHuaLowest {
int lineCount;
int[][] inputData;
public void run(){
input();
SortUtils.printArray(inputData);
calculate();
}
/*
* 输入数据
* */
private void input(){
Scanner in=new Scanner(System.in);
lineCount=Integer.parseInt(in.nextLine());
inputData=new int[lineCount][lineCount];
for(int i=0;i<lineCount;i++){
String line=in.nextLine();
String[] strs=line.split(" ");
int len=strs.length;
for(int j=0;j<len;j++){
inputData[i][j]=Integer.parseInt(strs[j]);
}
}
}
/**
* 获取路径最大值
*/
private void calculate(){
int max=getSumMax(0,0);
System.out.println("从顶到底路径最大值为:"+max);
}
private int getSumMax(int i,int j){
if(i==lineCount-1)
return inputData[i][j];
int x=getSumMax(i+1,j);
int y=getSumMax(i+1,j+1);
return Math.max(x, y)+inputData[i][j];
}
}
但是,这样子计算速度不敢恭维。想象一下,从matrix[0,0] 开始,第二次递归计算 maxSum[1,0] 和 maxSum[1,1] ,maxSum[1,0] 又计算 maxSum[2,0] 和 maxSum[2,1] 而且最为致命的是,如果我费了大半天已经计算出来 maxSum[2,1],但是下次计算maxSum[2,1] ,我仍然会递归循环计算该值,导致很多不必要的计算。
红色角标代表该位置到终点计算的次数。可以看出越是底层越是重复计算
那我们是不是可以将计算的储存结果储存起来,下次调用先查看该结果,如果有缓存了那么就直接使用就可以了,不用再次计算了。
很明显可以使用一个二维数组储存计算结果
代码如下
//递归算法
private int getSumMax(int i,int j){
if(outputData[i][j]!=-1)
return outputData[i][j];
int x=getSumMax(i+1,j);
int y=getSumMax(i+1,j+1);
int res=Math.max(x, y)+inputData[i][j];
outputData[i][j]=res;
return res;
}
再做一些优化,这种方法是使用递归算法的,无形中就使用了大量的堆栈资源,如果调用深度太深,那么就会造成栈溢出,所以我们可以使用递推算法实现,减少资源使用。
递推很明显只可以从底部开始往上递推,创建一个二维数组,从最后一行开始更新,过程如下
知道了思路,那么就开始实现吧
//非递归算法,递推算法实现
private int getSumMax(int i,int j){
for(int p=lineCount-1-1;p>=0;p--){
for(int q=0;q<=p;q++){
outputData[p][q]=Math.max(outputData[p+1][q], outputData[p+1][q+1])+inputData[p][q];
}
}
return outputData[0][0];
}
下面给出完整代码
package com.luo.dongtaiguihua;
import java.util.Scanner;
import com.luo.util.SortUtils;
/*
* 低阶动态规划
* 使用二维数组缓存,使用递归算法和递推算法实现
* */
public class DongTaiGuiHuaLower {
int lineCount;
int[][] inputData;
int[][] outputData;
public void run(){
input();
SortUtils.printArray(inputData);
calculate();
SortUtils.printArray(outputData);
}
/*
* 输入数据
* */
private void input(){
Scanner in=new Scanner(System.in);
lineCount=Integer.parseInt(in.nextLine());
inputData=new int[lineCount][lineCount];
for(int i=0;i<lineCount;i++){
String line=in.nextLine();
String[] strs=line.split(" ");
int len=strs.length;
for(int j=0;j<len;j++){
inputData[i][j]=Integer.parseInt(strs[j]);
}
}
}
/**
* 获取路径最大值
*/
private void calculate(){
initOutputData();
int max=getSumMax(0,0);
System.out.println("从顶到底路径最大值为:"+max);
}
/*
* 初始化用于缓存的数组
* */
private void initOutputData() {
outputData=new int[lineCount][lineCount];
for(int i=0;i<lineCount;i++){
outputData[lineCount-1][i]=inputData[lineCount-1][i];
}
for(int i=0;i<lineCount-1;i++){
for(int j=0;j<lineCount;j++){
outputData[i][j]=-1;
}
}
}
// //递归算法
// private int getSumMax(int i,int j){
// if(outputData[i][j]!=-1)
// return outputData[i][j];
// int x=getSumMax(i+1,j);
// int y=getSumMax(i+1,j+1);
// int res=Math.max(x, y)+inputData[i][j];
// outputData[i][j]=res;
// return res;
// }
//非递归算法,递推算法实现
private int getSumMax(int i,int j){
for(int p=lineCount-1-1;p>=0;p--){
for(int q=0;q<=p;q++){
outputData[p][q]=Math.max(outputData[p+1][q], outputData[p+1][q+1])+inputData[p][q];
}
}
return outputData[0][0];
}
}
其实这还可以再做一些优化,其实可以使用一维数组就够了
完整代码如下
package com.luo.dongtaiguihua;
import java.util.Scanner;
import com.luo.util.SortUtils;
/*
* 低阶动态规划
* 使用递推算法和一维数组实现
* */
public class DongTaiGuiHuaLow {
int lineCount;
int[][] inputData;
int[] outputData;
public void run(){
input();
SortUtils.printArray(inputData);
calculate();
SortUtils.printArray(outputData);
}
/*
* 输入数据
* */
private void input(){
Scanner in=new Scanner(System.in);
lineCount=Integer.parseInt(in.nextLine());
inputData=new int[lineCount][lineCount];
for(int i=0;i<lineCount;i++){
String line=in.nextLine();
String[] strs=line.split(" ");
int len=strs.length;
for(int j=0;j<len;j++){
inputData[i][j]=Integer.parseInt(strs[j]);
}
}
}
/**
* 获取路径最大值
*/
private void calculate(){
initOutputData();
int max=getSumMax(0,0);
System.out.println("从顶到底路径最大值为:"+max);
}
/*
* 初始化用于缓存的数组
* */
private void initOutputData() {
outputData=new int[lineCount];
for(int i=0;i<lineCount;i++){
outputData[i]=inputData[lineCount-1][i];
}
}
//非递归算法,使用递推算法实现
private int getSumMax(int i,int j){
for(int p=lineCount-1-1;p>=0;p--){
for(int q=0;q<=p;q++){
outputData[q]=inputData[p][q]+Math.max(outputData[q], outputData[q+1]);
}
}
return outputData[0];
}
}