Java算法——动态规划
基本思想:
动态规划算法通常用于求解具有某种最优性质的问题(作用就是求最优解)。在这类问题中,可能会有许多可行解。每一个解都对应于一个值,我们希望找到具有最优值的解。动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。
与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。
若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很多次。如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。我们可以用一个表来记录所有已解的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划法的基本思路。具体的动态规划算法多种多样,但它们具有相同的填表格式。
动态规划算法与分治法最大的差别是:适合于用动态规划法求解的问题,经分解后得到的子问题往往不是互相独立的(即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解)
应用场景:
适用动态规划的问题必须满足最优化原理、无后效性和重叠性。
1.最优化原理(最优子结构性质) 最优化原理可这样阐述:一个最优化策略具有这样的性质,不论过去状态和决策如何,对前面的决策所形成的状态而言,余下的诸决策必须构成最优策略。简而言之,一个最优化策略的子策略总是最优的。一个问题满足最优化原理又称其具有最优子结构性质。
2.无后效性 将各阶段按照一定的次序排列好之后,对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来的决策,而只能通过当前的这个状态。换句话说,每个状态都是过去历史的一个完整总结。这就是无后向性,又称为无后效性。
3.子问题的重叠性 动态规划将原来具有指数级时间复杂度的搜索算法改进成了具有多项式时间复杂度的算法。其中的关键在于解决冗余,这是动态规划算法的根本目的。动态规划实质上是一种以空间换时间的技术,它在实现的过程中,不得不存储产生过程中的各种状态,所以它的空间复杂度要大于其它的算法。
动态规划算法经典案例:
案例一:有n级台阶,一个人每次上一级或者两级,问有多少种走完n级台阶的方法。
分析:动态规划的实现的关键在于能不能准确合理的用动态规划表来抽象出实际问题。在这个问题上,我们让f(n)表示走上n级台阶的方法数。
那么当n为1时,f(n) = 1,n为2时,f(n) =2,就是说当台阶只有一级的时候,方法数是一种,台阶有两级的时候,方法数为2。那么当我们要走上n级台阶,必然是从n-1级台阶迈一步或者是从n-2级台阶迈两步,所以到达n级台阶的方法数必然是到达n-1级台阶的方法数加上到达n-2级台阶的方法数之和。即f(n) = f(n-1)+f(n-2),我们用dp[n]来表示动态规划表,dp[i],i>0,i<=n,表示到达i级台阶的方法数。
import java.util.Scanner; public class CalculationSteps { public static long calStep(int n) { //如果只有1阶,则只有一种,如果2阶,则有两种 if (n == 1 || n == 2) { return n; } else { long[] dp = new long[n]; //dp表的n-1号元素为0的话,那么就向前回一阶 if (dp[n - 1] == 0) { dp[n - 1] = calStep(n - 1); } //dp表的n-号元素为0的话,那么就向前回两阶 if (dp[n - 2] == 0) { dp[n - 2] = calStep(n - 2); } //总的方法就是跳1阶和跳两阶的和 return dp[n - 1] + dp[n - 2]; } } public static void main(String[] args) { Scanner sc = new Scanner(System.in); int n = sc.nextInt(); System.out.println(calStep(n)); }
案例2:给定一个矩阵m,从左上角开始每次只能向右走或者向下走,最后达到右下角的位置,路径中所有数字累加起来就是路径和,返回所有路径的最小路径和,如果给定的m如下,那么路径4,1,2,5,2,4,5就是最小路径和,返回23.
4 1 5 3
3 2 7 7
6 5 2 8
8 9 4 5
分析:对于这个题目,假设m是m行n列的矩阵,那么我们用dp[m][n]来抽象这个问题,dp[i][j]表示的是从原点到i,j位置的最短路径和。我们首先计算第一行和第一列,直接累加即可,那么对于其他位置,要么是从它左边的位置达到,要么是从上边的位置达到,我们取左边和上边的较小值,然后加上当前的路径值,就是达到当前点的最短路径。然后从左到右,从上到下依次计算即可。
/** * 给定一个矩阵m,从左上角开始每次只能向右走或者向下走 * 最后达到右下角的位置,路径中所有数字累加起来就是路径和, */ public class ShortestPath { public static int findShortestPath(int[][] arr) { int[][] dp = new int[arr.length][arr[0].length]; // 直接设置起始位置 dp[0][0] = arr[0][0]; // 计算第一列dp的值 for (int i = 1; i < arr.length; i++) { dp[i][0] = dp[i - 1][0] + arr[i][0]; } // 计算第一行dp的值 for (int i = 1; i < arr[0].length; i++) { dp[0][i] = dp[0][i - 1] + arr[0][i]; } // 补全dp规划矩阵 for (int i = 1; i < arr.length; i++) { for (int j = 1; j < arr[0].length; j++) { dp[i][j] = arr[i][j] + Math.min(dp[i - 1][j], dp[i][j - 1]); } } return dp[arr.length - 1][arr[0].length - 1]; } public static void print(int[][] arr) { for (int i = 0; i < arr.length; i++) { for (int j = 0; j < arr[i].length; j++) { System.out.print(arr[i][j] + " "); } System.out.println(); } } public static void main(String[] args) { int[][] arr = { { 4, 1, 5, 3 }, { 3, 2, 7, 7 }, { 6, 5, 2, 8 }, { 8, 9, 4, 5 } }; print(arr); System.out.println("最短路径长度:" + findShortestPath(arr)); } }
案例3:最长公共子序列问题
最长公共子序列问题是要找到两个字符串间的最长公共子序列。假设有两个字符串sudjxidjs和xidjxidpolkj,其中djxidj就是他们的最长公共子序列。许多问题都可以看成是公共子序列的变形。例如语音识别问题就可以看成最长公共子序列问题。
假设两个字符串分别为A=a1a2..am,B=b1b2..bn,则m为A的长度,n为B的长度。那么他们的最长公共子序列分为两种情况。
1、am=bn,这时他们的公共子序列一定为的长度F(m,n)=F(m-1,n-1)+am;
2、am≠bn,这时他们的公共子序列一定为的长度F(m,n)=Max(F(m-1,n),F(m,n-1));
public class Lcs { public static String findLcs(String A, String B) { //得到A,B的长度,根据长度建立dp表 int n = A.length(); int m = B.length(); //将字符串转换成数组 char[] a = A.toCharArray(); char[] b = B.toCharArray(); String result = ""; int[][] dp = new int[n][m]; //计算第一列dp的值 for (int i = 0; i < n; i++) { if (a[i] == b[0]) { dp[i][0] = 1; result += a[i]; for (int j = i + 1; j < n; j++) { dp[j][0] = 1; } break; } } //计算第一行dp的值 for (int i = 0; i < m; i++) { if (a[0] == b[i]) { dp[0][i] = 1; result += b[i]; for (int j = i + 1; j < m; j++) { dp[0][j] = 1; } break; } } //补全dp的其他位置 for (int i = 1; i < n; i++) { for (int j = 1; j < m; j++) { if (a[i] == b[j]) { dp[i][j] = dp[i - 1][j - 1] + 1; result += a[i]; } else { dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); } } } return result; } public static int getLength(String str) { return str.length(); } public static void main(String[] args) { String str1 = "bcdhiex"; String str2 = "abcdefg"; String result=findLcs(str1, str2); System.out.println(result); int length=getLength(result); System.out.println("公共子序列长度:"+length); } }