動態規划 - 石子合並問題


(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)總結  

  石子合並問題完全提現了動態規划的核心思想,先求解子問題的解【子問題求解不相互獨立,相互依賴】,然后從這些子問題的解中得到原問題的解,進而得到該問題的最優解。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM