动态规划小例子

来源:互联网 发布:棋牌游戏网站源码 编辑:程序博客网 时间:2024/04/30 12:59

上图给出了一个数字三角形。从三角形的顶部到底部有很多条不同的路径。对于每条路径,把路径上面的数加起来可以得到一个和,和最大的路径称为最佳路径。你的任务就是求出最佳路径上的数字之和。

注意:路径上的每一步只能从一个数走到下一层上和它最近的左边的数或者右边的数。

输入样例

5

7

3 8

8 1 0

2 7 4 4

4 5 2 6 5

输出样例

30


这道题目可以用递归的方法解决。基本思路是:

D(r, j)表示第r行第 j个数字(rj都从1开始算),以MaxSum(r,j)代表从第 r行的第 j个数字到底边的最佳路径的数字之和,则本题是要求MaxSum(1, 1)

从某个D(r,j)出发,显然下一步只能走D(r+1, j)或者D(r+1,j+1)。如果走D(r+1, j),那么得到的MaxSum(r,j)就是MaxSum(r+1, j) + D(r, j);如果走D(r+1,j+1),那么得到的MaxSum(r, j)就是MaxSum(r+1,j+1) + D(r, j)。所以,选择往哪里走,就看MaxSum(r+1, j)MaxSum(r+1,j+1)哪个更大了。


上面的程序,效率非常低,在N值并不大,比如N=100的时候,就慢得几乎永远算不出结果了。

为什么会这样呢?是因为过多的重复计算。

我们不妨将对MaxSum函数的一次调用称为一次计算。那么,每次计算MaxSum(r,j)的时候,都要计算一次MaxSum(r+1, j+1),而每次计算MaxSum(r,j+1)的时候,也要计算一次MaxSum(r+1, j+1)。重复计算因此产生。


n既然问题出在重复计算,那么解决的办法,当然就是,一个值一旦算出来,就要记住,以后不必重新计算。即第一次算出MaxSum(r,j)的值时,就将该值存放起来,下次再需要计算MaxSum(r,j)时,直接取用存好的值即可,不必再次调用MaxSum进行函数递归计算了。这样,每个MaxSum(r,j)都只需要计算1 次即可,那么总的计算次数(即调用MaxSum函数的次数)就是三角形中的数字总数,即1+2+3+…+N= N(N+1)/2。
n 如何存放计算出来的MaxSum(r,j)值呢?显然,用一个二维数组aMaxSum[N][N]就能解决。aMaxSum[r][j]就存放MaxSum(r,j)的计算结果。下次再需要MaxSum(r, j)的值时,不必再调用MaxSum函数,只需直接取aMaxSum[r][j]的值即可。

    这种将一个问题分解为子问题递归求解,并且将中间结果保存以避免重复计算的办法,就叫做“动态规划”。动态规划通常用来求最优解,能用动态规划解决的求最优解问题,必须满足,最优解的每个局部解也都是最优的。以上题为例,最佳路径上面的每个数字到底部的那一段路径,都是从该数字出发到达到底部的最佳路径。

实际上,递归的思想在编程时未必要实现为递归函数。在上面的例子里,有递推公式:



因此,不需要写递归函数,从aMaxSum[N-1]这一行元素开始向上逐行递推,就能求得aMaxSum[1][1]的值了。


code

package com.wyy.core.util;import java.io.PrintWriter;import java.util.Scanner;public class Main {    static Scanner scin = new Scanner(System.in);    static PrintWriter scout = new PrintWriter(System.out);    static int[][] all = new int[110][110];    static int[][] mem = new int[110][110];    public static void main(String[] args) {        int size = scin.nextInt();        for (int i = 1; i <= size; i++) {            for (int j = 1; j <= i; j++) {                all[i][j] = scin.nextInt();            }        }        scout.println(MaxSum(size, 1, 1));                for (int i = 1; i <= size; i++) {            for (int j = 1; j <= i; j++) {                scout.print(mem[i][j] + " ");            }            scout.println();        }        scout.flush();        scout.println("-----------------------------------------------");        mem = new int[110][110];                for (int j = 1; j <= size; j++)            mem[size][j] = all[size][j];                for (int i = size; i > 1; i--) {            for (int j = 1; j < i; j++) {                if (mem[i][j] > mem[i][j + 1])                    mem[i - 1][j] = mem[i][j] + all[i - 1][j];                else                    mem[i - 1][j] = mem[i][j + 1] + all[i - 1][j];            }        }        scout.println(mem[1][1]);        for (int i = 1; i <= size; i++) {            for (int j = 1; j <= i; j++) {                scout.print(mem[i][j] + " ");            }            scout.println();        }        scout.flush();        scout.close();    }    public static int MaxSum(int size, int r, int j) {        if (r == size) {            mem[r][j] = all[r][j];            return all[r][j];        }        if (mem[r + 1][j] == 0) {            mem[r + 1][j] = MaxSum(size, r + 1, j);        }        if (mem[r + 1][j + 1] == 0) {            mem[r + 1][j + 1] = MaxSum(size, r + 1, j + 1);        }                if (mem[r + 1][j] > mem[r + 1][j + 1]) {            System.out.println("MaxSum(" + r + ", " + j + ")=" + (mem[r + 1][j] + all[r][j]));            return mem[r + 1][j] + all[r][j];        }        System.out.println("MaxSum(" + r + ", " + j + ")=" + (mem[r + 1][j + 1] + all[r][j]));        return mem[r + 1][j + 1] + all[r][j];    }}

动态规划解题的一般思路 

  许多求最优解的问题可以用动态规划来解决。
  首先要把原问题分解为若干个子问题。注意单纯的递归往往会导致子问题被重复计算,用动态规划的方法,子问题的解一旦求出就要被保存,所以每个子问题只需求解一次。
  子问题经常和原问题形式相似,有时甚至完全一样,只不过规模从原来的n 变成了n-1,或从原来的n×m变成了n×(m-1)……等等。
  找到子问题,就意味着找到了将整个问题逐渐分解的办法。
  分解下去,直到最底层规模最小的的子问题可以一目了然地看出解。
  每一层子问题的解决,会导致上一层子问题的解决,逐层向上,就会导致最终整个问题的解决。
  如果从最底层的子问题开始,自底向上地推导出一个个子问题的解,那么编程的时候就不需要写递归函数。
  用动态规划解题时,将和子问题相关的各个变量的一组取值,称之为一个“状态”。一个“状态”对应于一个或多个子问题,所谓某个“状态”下的“值”,就是这个“状态”所对应的子问题的解。
  比如数字三角形,子问题就是“从位于(r,j)数字开始,到底边路径的最大和”。这个子问题和两个变量r和j相关,那么一个“状态”,就是r,j 的一组取值,即每个数字的位置就是一个“状态”。该“状态”所对应的“值”,就是从该位置的数字开始,到底边的最佳路径上的数字之和。
  定义出什么是“状态”,以及在该“状态”下的“值”后,就要找出不同的状态之间如何迁移――即如何从一个或多个“值”已知的“状态”,求出另一个“状态”的“值”。状态的迁移可以用递推公式表示,此递推公式也可被称作“状态转移方程”。
  用动态规划解题,如何寻找“子问题”,定义“状态”,“状态转移方程”是什么样的,并没有一定之规,需要具体问题具体分析,题目做多了就会有感觉。
  甚至,对于同一个问题,分解成子问题的办法可能不止一种,因而“状态”也可以有不同的定义方法。不同的“状态”定义方法可能会导致时间、空间效率上的区别。




原创粉丝点击