暴力递归 转 动态规划

介绍递归和动态规划

暴力递归:

1,把问题转化为规模缩小了的同类问题的子问题 

2,有明确的不需要继续进行递归的条件(base case) 

3,有当得到了子问题的结果之后的决策过程

 4,不记录每一个子问题的解

动态规划 

1,从暴力递归中来 

2,将每一个子问题的解记录下来,避免重复计算

 3,把暴力递归的过程,抽象成了状态表达 

4,并且存在化简状态表达,使其更加简洁的可能

什么是暴力递归?

理解下递归

首先一个最简单的递归

public static int total(int n){
		if(n==1){
			return 1;
		}
		return n+total(n-1);
	}

计算一个数的累加

深度分析下这个递归。

比如要求 1到 5 的递归

total(5)

把问题转化为规模缩小了的同类问题的子问题 

求 1-5 的累加和。    转化成了  5 + total(4)        5加上 1-4 的累加和

求 1-4 的累加和。    转化成了  4 + total(3)        4加上 1-3 的累加和

求 1-3 的累加和。    转化成了  3 + total(2)        3加上 1-2 的累加和

求 1-2 的累加和。    转化成了  2 + total(1)        2加上 1-1 的累加和

1-1的累加和不需要去求。因为这里是递归的终止条件 base case

此时得到了最后一个子问题的解

应当处理子问题↓

有当得到了子问题的结果之后的决策过程

total(1) = 1、total(2) = 1+2、total(3) = 1+2+3、total(4) = 1+2+3+4、total(5)=1+2+3+4+5

得出最后结果。


得到所有子问题并利用子问题的解去求。之前想要的问题↑

public static int total2(int n){
		
		int[] dp = new int[n+1];
		//dp[1] 存放的就是 total(1) 的解
		//dp[2] 存放的就是 total(2) 的解
		//dp[3] 存放的就是 total(3) 的解
		//dp[4] 存放的就是 total(4) 的解
		//dp[5] 存放的就是 total(5) 的解...
		for(int i=1;i<n+1;i++){
			dp[i] = total(i);
		}
		return dp[5];
	}

倘若使用动态规划的话,就是把每一步的解保存下来。上面的做法有点笨。只是为方便理解

可以通过上一个子问题的解求出下一个问题的

public static int total2(int n){
		int[] dp = new int[n+1];
		//dp[1] 存放的就是 total(1) 的解
		//dp[2] 存放的就是 total(2) 的解
		//dp[3] 存放的就是 total(3) 的解
		//dp[4] 存放的就是 total(4) 的解
		//dp[5] 存放的就是 total(5) 的解...
		for(int i=1;i<n+1;i++){
			dp[i] = dp[i]+i;
		}
		return dp[5];
	}

上↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑

这样去求累加和只是为了更好的理解动态规划

 暴力递归 :不记录每一个子问题的解

 动态规划 :记录每一个子问题的解


最短路径问题

给你一个二维数组,二维数组中的每个数都是正数,要求从左上 角走到右下角,每一步只能向右或者向下。沿途经过的数字要累 加起来。返回最小的路径和


求从左上角走到右下角的最短距离

普通递归去做这道题。

暴力递归:测试所有可能的方案


把每条可以走的路都,走一遍试一下。试试那条路最近。

public static int process1(int[][] arr,int i,int j){
		//走到了最后,将最后的值返回,走每一条路都需要加上最后的值
		if(i==arr.length-1&&j==arr[0].length-1){
			return arr[i][j];
		}
		
		//下方走到头了,只能向右走
		if(i==arr.length-1){
			return arr[i][j] + process1(arr,i,j+1);
		}
		//右边走到头了,只能向下走
		if(j==arr[0].length-1){
			return arr[i][j] + process1(arr,i+1,j);
		}
		
		//下、右都能走。 看看那条路的代价最小。
		return arr[i][j] + Math.min(process1(arr,i+1,j), process1(arr,i,j+1));
	}

这个递归相当于有分解成了一堆子问题。

0,0到2,2的最短距离 变成了

1,0 到 2,2 的最短距离 

0,1 到 2,2 的最短距离

他们两条路线那个最短距离短,就选择走那条路。

.........................

把每个坐标都理解成一个函数。求他到终点的最短距离

↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓


首先必须得理解暴力递归。才能写出动态规划。

经过分析。发现好几条路的最短路径已经确定。 可是下一次去求的时候还是会重复计算一次。

这里我们可以用到

记忆化搜索(傻缓存)<这不是完全的动态规划>

用一个全局变量

把函数的每一个子状态结果存储下来。

每一调用子过程的时候,去cache里面查询一下。

string  = 存储坐标

integer = 该坐标到最终距离的最短距离

//记忆化搜索
	static HashMap<String,Integer> cache = new HashMap<String,Integer>();
	public static int process2(int[][] matrix,int i,int j){
		if(i==matrix.length-1&&j==matrix[0].length-1){
			return matrix[i][j];
		}
		if(i==matrix.length-1){
			int right;
			String index= i+","+(j+1);
			if(!cache.containsKey(index)){
				right = process2(matrix,i,j+1);
				cache.put(index,right);
			}
			right = cache.get(index);
			return matrix[i][j] + right;
		}
		
		if(j==matrix[0].length-1){
			int down;
			String index= (i+1)+","+j;
			if(!cache.containsKey(index)){
				down = process2(matrix,i+1,j);
				cache.put(index,down);
			}
			down = cache.get(index);
			return matrix[i][j] + down;
		}
		
		
		int right;
		int down;
		String indexr= i+","+(j+1);
		String indexd= (i+1)+","+j;
		if(!cache.containsKey(indexr)){
			right = process2(matrix,i,j+1);
			cache.put(indexr,right);
		}
		if(!cache.containsKey(indexd)){
			down = process2(matrix,i+1,j);
			cache.put(indexd,down);
		}
	return matrix[i][j]+Math.min(cache.get(indexr),cache.get(indexd));
	}

cache 里的数据要理解成 每个不同函数 ,的解。

动态规划解这道题

把暴力递归的过程,抽象成了状态表达 

暴力递归转化成动态规划。完全不需要去考虑题意本身。

只需要关注暴力递归。这一段代码


准备一个数组。他是用来盛放每一个子问题的解的

0,0位置  存放 0,0 到最终2,2的解

0,1位置 存放 0,1 到最终2,2的解

....

在所有的解 里面 2,2 位置是可以确定的。因为。他就是终止条件


通过 6 可以确定 1,2的最短距离。0,2的最短距离。 因为。他们只能向下走走到 2,2

同理  2,0,     2,1 只能通过 向右走  走到2,2


切记每一个表格位置,存放的是该函数的解

此时。根据递归函数。

1,1的位置应该 是判断 2,1     1,2   两个解那个小。 自己的路径值加上那个小的解

 

其他位置同理。可以推理出来


二维数组 0,0 位置就是要求的值。 0,0 到 2,2 的最短距离

这个二维数组里面存放的是 所有  子问题   (函数)  的结果

他是通过递归函数。所有的依赖关系倒着推理出来的。

他和原本的问题没有任何关系。

写出动态规划。需要的就是理解递归。用表格存储下所有子问题的解。

动态规划就是填表格。

//动态规划,就是递归的过程完全理解后,递归出所有位置的解集
	public static int process3(int[][] arr){
		int row = arr.length-1;
		int low = arr[0].length-1;
		//构建一个存放所有解的二维数组
		int[][] dp = new int[row+1][low+1];
		
		//最后位置的值是可以确定的 因为他是 base case
		dp[row][low] = arr[row][low];
		
		for(int i=row-1;i>=0;i--){
			dp[i][low] = arr[i][low] + dp[i+1][low];
		}
		
		for(int i=low-1;i>=0;i--){
			dp[row][i] = arr[row][i] + dp[row][i+1];
		}
		
		for(int i=row-1;i>=0;i--){
			for(int j=low-1;j>=0;j--){
				dp[i][j] = Math.min(arr[i][j]+dp[i+1][j],arr[i][j]+ dp[i][j+1]);
			}
		}
		return dp[0][0];
	}

将暴力递归改成动态规划的步骤

1、分析可变参数,确定表格空间。(所有解的用数组表示。应该用多大的数组。)

(如果有3个可变参数,就应该用一个3维数组表示。所有的解)

2、确定最终状态(base case)

3、确定初始状态(需要得到的结果)

4、分析一个普遍位置依赖那些位置

5、根据依赖位置顺序。填表格



最短路径全部代码


package basic_class_07;
import java.util.*;
public class Test07 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[][]  a =  { { 1, 3, 5 }, { 8, 1, 3 }, { 5, 0, 6}  };
		System.out.println(process2(a,0,0));
		System.out.println(process1(a,0,0));
		System.out.println(process3(a));
	}
	public static int process1(int[][] arr,int i,int j){
		//走到了最后,将最后的值返回,走每一条路都需要加上最后的值
		if(i==arr.length-1&&j==arr[0].length-1){
			return arr[i][j];
		}
		
		//下方走到头了,只能向右走
		if(i==arr.length-1){
			return arr[i][j] + process1(arr,i,j+1);
		}
		//右边走到头了,只能向下走
		if(j==arr[0].length-1){
			return arr[i][j] + process1(arr,i+1,j);
		}
		
		//下、右都能走。 看看那条路的代价最小。
		return arr[i][j] + Math.min(process1(arr,i+1,j), process1(arr,i,j+1));
	}
	
	
	
	//记忆化搜索
	static HashMap<String,Integer> cache = new HashMap<String,Integer>();
	public static int process2(int[][] matrix,int i,int j){
		if(i==matrix.length-1&&j==matrix[0].length-1){
			return matrix[i][j];
		}
		if(i==matrix.length-1){
			int right;
			String index= i+","+(j+1);
			if(!cache.containsKey(index)){
				right = process2(matrix,i,j+1);
				cache.put(index,right);
			}
			right = cache.get(index);
			return matrix[i][j] + right;
		}
		
		if(j==matrix[0].length-1){
			int down;
			String index= (i+1)+","+j;
			if(!cache.containsKey(index)){
				down = process2(matrix,i+1,j);
				cache.put(index,down);
			}
			down = cache.get(index);
			return matrix[i][j] + down;
		}
		
		
		int right;
		int down;
		String indexr= i+","+(j+1);
		String indexd= (i+1)+","+j;
		if(!cache.containsKey(indexr)){
			right = process2(matrix,i,j+1);
			cache.put(indexr,right);
		}
		if(!cache.containsKey(indexd)){
			down = process2(matrix,i+1,j);
			cache.put(indexd,down);
		}
	return matrix[i][j]+Math.min(cache.get(indexr),cache.get(indexd));
	}
	
	//动态规划,就是递归的过程完全理解后,递归出所有位置的解集
	public static int process3(int[][] arr){
		int row = arr.length-1;
		int low = arr[0].length-1;
		//构建一个存放所有解的二维数组
		int[][] dp = new int[row+1][low+1];
		
		//最后位置的值是可以确定的 因为他是 base case
		dp[row][low] = arr[row][low];
		
		for(int i=row-1;i>=0;i--){
			dp[i][low] = arr[i][low] + dp[i+1][low];
		}
		
		for(int i=low-1;i>=0;i--){
			dp[row][i] = arr[row][i] + dp[row][i+1];
		}
		
		for(int i=row-1;i>=0;i--){
			for(int j=low-1;j>=0;j--){
				dp[i][j] = Math.min(arr[i][j]+dp[i+1][j],arr[i][j]+ dp[i][j+1]);
			}
		}
		return dp[0][0];
	}
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SUNbrightness

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值