• 动态规划


    (1)问题描述

      在一个圆形操场的四周摆放着 num 堆石子。先要将石子有次序地合并成一堆。规定每次只能选相邻的 2 堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的耗费力气。试设计一个算法,计算将 n 堆石子合并成一堆的最省力气数。

    (2)算法思想

      对于给定的 n 堆石子,当只有一堆时,不用搬,进而不耗费力气,然后依次计算出从 2 堆 ~ num 堆石子的最优解,并且堆数递增求最优解,依赖于上一步的解进行计算所得;

    (3)算法思路

      此解法和矩阵连乘类似,我们知道矩阵连乘也是每次合并相邻的两个矩阵,那么石子合并可以用矩阵连乘的方式来解决。

      设 dp[i][j] 表示第 i 到第 j 堆石子合并的最优值,sum[i][j] 表示第 i 到第 j 堆石子的所耗费的力气总数。动规方程如下: 
      这里写图片描述

    (4)代码展示

    public class StoneMerge {
    
        /**
         * 记录石子堆的数量
         */
        private static int num;
    
        /**
         * 记录每堆石子的重量
         */
        private static int[] weight;
    
        /**
         * 记录石子堆断开的位置【便于计算局部最优解】
         */
        private static int[][] location;
    
        /**
         * 记录石子堆局部最优解,以至于求得最终最优解【动规方程】
         */
        private static int[][] dp;
    
        /**
         * 初始化数据
         */
        private static void initData() {
            Scanner input = new Scanner(System.in);
            System.out.println("请输入石子堆数量:");
            num = input.nextInt();
    
            weight = new int[num];
            System.out.println("请输入每堆石子的重量:");
            for (int i = 0; i < weight.length; i++) {
                weight[i] = input.nextInt();
            }
    
            // 定义成 int 类型的二维数组,创建完每个元素直接初始化为 0 
            dp = new int[num][num];
            location = new int[num][num];
        }
    
        /**
         * 计算最省最费力气值
         */
        private static void dpFindMinStrength() {
            // 初始化 dp 数组
            for (int m = 0; m < num; m++) {
                dp[m][m] = 0;                                           // 一堆石子,不用搬,耗费力气为 0
            }
    
            for (int r = 2; r <= num; r++) {                            // 从 2 堆依次到 num 堆,分别计算最优值
                for (int i = 0; i < num - r + 1; i++) {                 // 起始石子堆取值范围
                    int j = i + r - 1;                                  // 根据每次选取石子堆 r 和起始石子堆 i ,计算终止石子堆
                    int sum = 0;
                    for (int x = i; x <= j; x++) {                      // 计算从石子堆 i 到 石子堆 j 合并时,最后两堆使用的力气总和 sum
                        sum += weight[x];
                    }
                    // 根据动规方程,从局部最优解中计算当前从石子堆 i 到石子堆 j 合并所使用的的力气总和
                    dp[i][j] = dp[i + 1][j] + sum;                      // 计算从 i 石子堆分开时,使用的力气总和
                    location[i][j] = i;                                 // 标记从第 i 石子堆分开位置
    
                    for (int k = i + 1; k < j; k++) {                   // 需要统计从 k 【k ∈ (i, j)】石子堆分开,使用的力气总和
                        int temp = dp[i][k] + dp[k + 1][j] + sum;       // 计算从 k 石子堆分开时,使用的力气总和
                        if (temp < dp[i][j]) {
                            dp[i][j] = temp;
                            location[i][j] = k;
                        }
                    }
                }
            }
        }
    
        /**
         * 输出
         */
        private static void print() {
            System.out.println("动规数组【不同堆数合并石子所费力气】:");
            for (int i = 0; i < num; i++) {
                for (int j = 0; j < num; j++) {
                    System.out.print(dp[i][j] + " ");
                }
                System.out.println();
            }
            System.out.println("不同堆数合并石子最省力气断开位置最优解:");
            for (int i = 0; i < num; i++) {
                for (int j = 0; j < num; j++) {
                    System.out.print(location[i][j] + " ");
                }
                System.out.println();
            }
        }
        
        public static void main(String[] args) {
            // 初始化数据
            initData();
    
            // 计算最省最费力气值
            dpFindMinStrength();
    
            // 输出
            print();
        }
    
    }
    石子合并核心代码

    (5)输入输出

    请输入石子堆数量:
    4 
    请输入每堆石子的重量:
    4 4 5 9
    动规数组【不同堆数合并石子所费力气】:
    0 8 21 43 
    0 0 9 27 
    0 0 0 14 
    0 0 0 0 
    不同堆数合并石子最省力气分开位置最优解【下标从数组 0 开始分开】:
    0 0 1 2 
    0 0 1 2 
    0 0 0 2 
    0 0 0 0 
    输入输出

    (6)总结  

      石子合并问题完全提现了动态规划的核心思想,先求解子问题的解【子问题求解不相互独立,相互依赖】,然后从这些子问题的解中得到原问题的解,进而得到该问题的最优解。

  • 相关阅读:
    POJ 3037 Skiing(Dijkstra)
    HDU 1875 畅通工程再续(kruskal)
    HDU 1233 还是畅通工程(Kruskal)
    Java实现 LeetCode 754 到达终点数字(暴力+反向)
    Java实现 LeetCode 754 到达终点数字(暴力+反向)
    Java实现 LeetCode 754 到达终点数字(暴力+反向)
    Java实现 LeetCode 753 破解保险箱(递归)
    Java实现 LeetCode 753 破解保险箱(递归)
    Java实现 LeetCode 753 破解保险箱(递归)
    Java实现 LeetCode 752 打开转盘锁(暴力)
  • 原文地址:https://www.cnblogs.com/blogtech/p/12307975.html
Copyright © 2020-2023  润新知