动态规划的理论分析

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/shengqianfeng/article/details/102612565

动态规划适合解决什么问题?

动态规划是一个很成熟的思想,很多人都对这些思想做了总结,可以用一句话概括就是:一个模型三个特征。
一个模型:即多阶段决策最优解模型,顾名思义,问题解决过程中包括多个阶段,经过每个阶段的决策可以产生该阶段对应的状态,寻找一组决策,经过这组决策可以得到希望最终得到的最优值。
三个特征:最优子结构、无后向性、重复子问题。

  • 最优子结构:我们可以通过子问题的最优解推导出问题的最优解,也就是通过前一阶段的状态可以推导出后一阶段的状态。
  • 无后向性:我们只关心前一阶段的状态,而不需要知道前一阶段的状态是怎么推导出来的;而且前阶段的状态一旦确定不会受后一阶段状态的影响。
  • 重复子问题:不同的决策序列达到相同的某个阶段,可能会产生相同的状态。对应到使用回溯思想,当画出递归树时发现递归树中有重复的节点。

在这里插入图片描述
题目:如图是一个n乘以n的矩阵w[i][j],矩阵中的每个位置的空位存储的是自然数值,我们要将棋子从左上角的起始位置搬移到右下角的终点位置,每次只能向下或者向右,不能后退。从起始位置到达终点位置有很多路径可以走,我们把每条路径经过的数字加起来作为路径经过的长度。如何从(0,0)起点位置走到(n-1,n-1)终点位置,可以得到到达终点的最短路径长度?
分析:定义状态min_dist(i,j),i表示行,j表示列,min_dist(i,j)表示从(0,0)到(i,j)经过的最短路径长度。那么这个问题就是一个多阶段最优解模型,可以使用动态规划的思想去解决。

min_dist(i, j) = w[i][j] + min(min_dist(i, j-1), min_dist(i-1, j))

其实min_dist(i,j)就等于(i,j)存储的数值加上min_dist(i,j-1)和min_dist(i-1,j)的最小值。也就是(i,j)的上方元素和左方元素的最小值。这就是最优子结构。这个公式也是递归思想解决这个问题时用到的状态转移方程

使用递归思想和状态转移方程求解棋盘的最短路径问题

package com.study.algorithm.recursion;

/**
 * @Auther: JeffSheng
 * @Date: 2019/10/17 16:20
 * @Description:
 * 使用递归思想和状态转移方程求解棋盘数组的最短路径问题
 * 状态转移方程:
 *            min_dist(i, j) = w[i][j] + min(min_dist(i, j-1), min_dist(i-1, j))
 */
public class MinDist{


    private static int[][] matrix =  {{1,3,5,9}, {2,1,3,4},{5,2,6,7},{6,8,4,3}};

    private static int n = 4;

    private static int[][] mem = new int[4][4];

    /**
     * 调用 minDist(n-1, n-1);
     * @param i
     * @param j
     * @return
     */
    public static int minDist(int i, int j) {
        if (i == 0 && j == 0) {
            return matrix[0][0];
        }
        if (mem[i][j] > 0) {
            return mem[i][j];
        }
        int minLeft = Integer.MAX_VALUE;
        if (j-1 >= 0) {
            minLeft = minDist(i, j-1);
        }
        int minUp = Integer.MAX_VALUE;
        if (i-1 >= 0) {
            minUp = minDist(i-1, j);
        }
        int currMinDist = matrix[i][j] + Math.min(minLeft, minUp);

        mem[i][j] = currMinDist;
        return currMinDist;
    }

    public static void main(String[] args) {
        int min =  minDist(n-1, n-1);;
        System.out.println(min);
    }

}

贪心、动态规划都可以用回溯解决,那么回溯是这么解决的,如下代码所示:

使用回溯思想+备忘录解决棋盘的最短路径走法问题

package com.study.algorithm.backtracking;

/**
 * @Auther: JeffSheng
 * @Date: 2019/10/17 14:25
 * @Description:
 * 使用回溯思想解决棋盘的最短路径走法问题
 */
public class MinDist {
    /**
     * 全局变量或者成员变量
     */
    private int minDist = Integer.MAX_VALUE;

    private int n = 3;

    private int[][] w = {{1,3,5,9}, {2,1,3,4}, {5,2,6,7}, {6,8,4,3}};

    /**
     * 1 3 5 9
     * 2 1 3 4
     * 5 2 6 7
     * 6 8 4 3
     *
     * 调用方式:minDistBacktracing(0, 0, 0, w, n);
     * @param i 横坐标 0~3
     * @param j 纵坐标 0~3
     * @param dist 表示从起点(0,0)到达(i,j)之前走过的路径长度,比如起始状态(0,0)时走到(0,0)时走过的dist为1
     * @param w 棋盘数组
     * @param n 数组长度
     * 要求:只能向右走或者向下走,不能后退
     */
    public void minDistBT(int i, int j, int dist, int[][] w, int n) {
        // 到达了 (n-1, n-1) 这个位置了,走过的最短路径是dist
        //到达最后一个元素的位置了
        if (i == n && j == n) {
            if (dist < minDist) {
                minDist = dist;
            }
            return;
        }

        /**
         * 在(i,j)时往下走和往右走都走一遍
         */
        // 往下走,更新 i=i+1, j=j
        if (i < n) {
            minDistBT(i + 1, j, dist + w[i][j], w, n);
        }
        // 往右走,更新 i=i, j=j+1
        if (j < n) {
            minDistBT(i, j+1, dist + w[i][j], w, n);
        }
    }


    public static void main(String[] args) {
        MinDist md = new MinDist();
        md.minDistBT(0,0,0, md.w,md.n);
        System.out.println(md.minDist+md.w[md.n][md.n]);
    }
}

那么如何使用动态规划思想来解决这个问题呢?
思想是首先画出状态转移表,其实就是一个二维数组,每个状态在二维数组中对应的是行、列、数组值三个元素。然后根据决策先后顺序,根据递归关系,分阶段填充状态表的每个状态。最后将这个递推填表的过程翻译成代码,就是动态规划的方式了。
如图所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
将状态转义表的填充过程翻译成代码如下:

使用动态规划思想解决棋盘的最短路径走法问题

package com.study.algorithm.dp;

/**
 * @Auther: JeffSheng
 * @Date: 2019/10/17 15:57
 * @Description:
 * 使用动态规划的思想解决棋盘的最短路径走法问题
 */
public class MinDist {
    private int n = 4;
    private int[][] w = {{1,3,5,9}, {2,1,3,4}, {5,2,6,7}, {6,8,4,3}};


    /**
     * 1 3 5 9
     * 2 1 3 4
     * 5 2 6 7
     * 6 8 4 3
     * @param matrix
     * @param n
     * @return
     */
    public int minDistDP(int[][] matrix, int n) {
        int[][] states = new int[n][n];
        int sum = 0;
        // 初始化 states 的第一行数据
        for (int j = 0; j < n; ++j) {
            sum += matrix[0][j];
            states[0][j] = sum;
        }
        sum = 0;
        // 初始化 states 的第一列数据
        for (int i = 0; i < n; ++i) {
            sum += matrix[i][0];
            states[i][0] = sum;
        }
        /**
         * 第一行和第一列数据填充好之后,继续依次填充其他数据
         * 规则是:将待填充的数据加上左侧和上侧进行比较取最小值,得出就是(i,j)这个位置的最优解
         */
        for (int i = 1; i < n; ++i) {
            for (int j = 1; j < n; ++j) {
                states[i][j] =
                        matrix[i][j] + Math.min(states[i][j-1], states[i-1][j]);
            }
        }
        return states[n-1][n-1];
    }

    public static void main(String[] args) {
        MinDist md = new MinDist();
        int min =  md.minDistDP(md.w,md.n);
        System.out.println(min);
    }
}

猜你喜欢

转载自blog.csdn.net/shengqianfeng/article/details/102612565