動的計画法(DP)

本旨

難しい問題を解くとき、最初にサブ問題を解き、次に大きな問題を徐々に解くことによって、問題は離散的なサブ問題に分解されます。

分割統治アルゴリズムとの類似点と相違点

  • 類似点:両方の基本的な考え方は、解決する問題をいくつかのサブ問題に分解し、最初にサブ問題を解決し、次にこれらのサブ問題の解決策から元の問題の解決策を取得することです。
  • 違い:分割統治アルゴリズムによって分解されたサブ問題は、多くの場合、独立しており、クイックソートなどの相互に関連していません。動的計画分解によって得られるサブ問題は、多くの場合、互いに独立していません。つまり、次のサブ問題は、前のサブ問題に基づいて解決されることがよくあります。

古典的な問題1-01ナップサック問題

あなたが4ポンドを収納できるバックパックを持っている泥棒なら、盗むことができる商品は3種類あります:
1)オーディオ($ 3,000、4ポンド)
2)ラップトップ($ 2,000、3ポンド)
3)ギター($ 1,500、1ポンド)
盗品をより価値のあるものにするために、どの商品を選ぶべきですか?値は何ですか?

1.簡単な方法

考えられるさまざまな製品の組み合わせを試して、最も価値の高い組み合わせを見つけてください。2 3 2 ^ 3の3アイテム23種類の組み合わせ方法では、商品の種類が増えると、この方法は非常に遅く、時間計算量はO(2 n)O(2 ^ n)であることがわかります。O 2n

2.欲張りアルゴリズム

選択ごとに、条件を満たす製品の単価が最も高い製品を選択してください。オーバーヘッドの少ない近似解を見つけることができます。この問題では、欲張りアルゴリズムを使用した最終結果は「音」ですが、近似解は必ずしも最適解ではありません。実際に盗まれた製品の最大値は「ラップトップ+」です。ギター"。

3.動的計画法

4ポンドの容量のバックパックの問題を解決するとき、最初に3ポンドの容量のバックパック、2ポンド...、1ポンド...の問題を解決できます。3つのものから2つのもの...、1つのもの...に最高の値を選択する問題を単純化します。サブ問題を解決することにより、元の大きな問題が段階的に解決されます。

すべての動的計画法アルゴリズムはグリッドから始まり、ナップサック問題のグリッドは次のとおりです。
ここに画像の説明を挿入します
テーブルを行ごとに(または左から右に列ごとに)上から下に入力するだけで済みます。テーブルがいっぱいになると、質問に対する回答が得られます。

  • ギターショップ:盗まれるのはギターだけで、ギターの重さは1ポンドなので、ギターショップは「ギター、$ 1,500」を追加する必要があります。
  • オーディオライン:オーディオの重量は4ポンドです。オーディオの値がギターの値よりも高いため、最初の3つのグリッドはギターを盗むことしかできません。4番目のグリッドは「オーディオ、$ 3,000」を追加します。
  • ラップトップ:ラップトップの重量は3ポンドであるため、最初の2つのグリッドは同じままで、ギターのままです。3番目のグリッドは、ラップトップの価格がギターよりも高いため、ラップトップを追加します。4番目のグリッドは、ラップトップであるためです。ギターはスピーカーよりも高価なので、この質問への答えである「ラップトップ、ギター、3,500ドル」を追加する必要があります。

最終的なグリッドは、次の画像のようになります。
ここに画像の説明を挿入します
上記の分析により、次の式で各グリッドの値を計算できます。
ここに画像の説明を挿入します
実装コードは次のとおりです。

public class KnapsackProblem {
    
    

    public static void knapsackProblem(int[] weight, int[] value, int capacity) {
    
    

        //记录不同容量不同商品数量的总价值
        //为了方便理解和编写,我们从数组的第一行第一列开始统计
        int[][] totalValue = new int[weight.length + 1][capacity + 1];
        //记录对应位置应该装的商品id
        String[][] goods = new String[weight.length + 1][capacity + 1];

        //初始化:避免null, 与算法无关
        for (int i = 0; i < totalValue.length; i++) {
    
    
            goods[i][0] = "";
        }
        for (int j = 0; j < totalValue[0].length; j++) {
    
    
            goods[0][j] = "";
        }

        for (int i = 1; i < totalValue.length; i++) {
    
    
            for (int j = 1; j < totalValue[i].length; j++) {
    
    
                //判断每个网格放入的内容
                if (weight[i - 1] <= j) {
    
    
                    int temp = value[i - 1] + totalValue[i - 1][j - weight[i - 1]];
                    if (temp > totalValue[i - 1][j]) {
    
    
                        totalValue[i][j] = temp;
                        goods[i][j] = (i - 1) + " " + goods[i - 1][j - weight[i - 1]];
                    } else {
    
    
                        totalValue[i][j] = totalValue[i - 1][j];
                        goods[i][j] = goods[i - 1][j];
                    }

                } else {
    
    
                    totalValue[i][j] = totalValue[i - 1][j];
                    goods[i][j] = goods[i - 1][j];
                }
            }
        }

        System.out.println("应该偷的商品序号:" + goods[weight.length][capacity]);
        System.out.println("商品总价值:" + totalValue[weight.length][capacity]);

    }

    public static void main(String[] args) {
    
    
        int[] weight = new int[]{
    
    1, 4, 3};
        int[] value = new int[]{
    
    1500, 3000, 2000};
        int capacity = 4;
        knapsackProblem(weight, value, capacity);

    }

}

この問題が動的計画法で解決できるかどうかを判断するにはどうすればよいですか?

①特定のインデックスを特定の制約の下で最適化する必要がある場合は、動的計画法を使用できます。
②問題をサブ問題に分解し、サブ問題の解決を推進して問題を解決することができます。

動的計画法を使用して問題を解決するにはどうすればよいですか?

①各動的計画法ソリューションにはグリッドが含まれます。グリッド内の各セルは問題のサブ問題を表します。したがって、最も重要なことは、問題をサブ問題に分割する方法です。 グリッドに対応する座標軸は何ですか?
②セルの値に何を追加する必要がありますか?セルの値は通常、最適化する値です。たとえば、ナップサック問題では、セルの値は商品の値です。

古典的な問題2-最長共通部分列

2つの文字列が与えられた場合、それらの最長共通部分列を見つけます。クリックのためにここに詳細な質問

この問題を分解して、これら2つの文字列部分文字列の最長共通部分列を解くことができます。
例:シーク"fosh""fish"最長共通部分列、最初"f""f"最長共通部分列をシーク"fo"、次にシーク"f"最長共通部分列を探すことができます。次の質問は、上記をもう一度解くことに基づいて問題を解決することです。したがって、グリッドは次のようになります。次の図に示すように、
ここに画像の説明を挿入します
グリッドを取得した、グリッドにデータを徐々に追加して、各グリッドの計算式を導き出します。最終的なグリッドの結果は次のとおりです。
ここに画像の説明を挿入します
データを追加する際に各グリッドの計算式を同期的に分析すると、次の式が得られます。
ここに画像の説明を挿入します
コードは次のように表示されます。

public class LongestCommonSubsequence {
    
    
    public int longestCommonSubsequence(String text1, String text2) {
    
    
        //为了方便计算,网格分别再第一行和第一列前插入空行和空列
        int[][] grid = new int[text1.length() + 1][text2.length() + 1];
        //因此,此处从1开始
        for (int i = 1; i < grid.length; i++) {
    
    
            for (int j = 1; j < grid[i].length; j++) {
    
    
                //此处访问text中的值要减1
                if (text1.charAt(i - 1) == text2.charAt(j - 1)) {
    
    
                    grid[i][j] = grid[i-1][j-1] + 1;
                } else {
    
    
                    grid[i][j] = Math.max(grid[i - 1][j], grid[i][j - 1]);
                }
            }
        }
        //返回最终结果
        return grid[text1.length()][text2.length()];
    }

    //测试代码
    public static void main(String[] args) {
    
    
        System.out.println(new LongestCommonSubsequence().longestCommonSubsequence("fosh", "fish"));
    }
}

参照:「アルゴリズム図」第9章動的計画法

おすすめ

転載: blog.csdn.net/AmorFati1996/article/details/111195130