デジタルマトリックスと最小経路 - 動的プログラミング(III)は暴力再帰への道を最適化

タイトル

行列mは、トップから始まり、左または右にはダウンし、そして最後に右下隅、すべての数字のパスがアップに追加し、パスで、すべてのパスは、パスと最小値を返すの位置に到達することができます。

例えば

次のようメートルを考えます:

1 3 5 9
8 1 3 4
5 0 6 1
8 8 4 0

1,3,1,0,6,1,0パスは、返された12すべてのパスとパスの最小であります

思考

  1. サブ問題に分割することができ、最適なサブ構造を、考えてみましょう。頂点行列の先頭から、最短経路を見つけると、唯一のダウンまたは右、次の2つの新たな状況、その観点と二つの新しいマトリックスの頂点下の点を右に考えることができるので、ちょうど見つけます両方の最小経路マトリックスと小さく、プラス現在の頂点の値はマトリックス全体最小経路であるとこと。知ります
  2. 再帰的な解決策が書かれた後は、規制措置は、メモリタイプの再帰の過度の使用など、問題を解決することが適当であり、サブ問題が重なっています。
  3. データメモリの依存関係分析を算出順に定義することができ、依存計算された第1の値は徐々に最終値を得るために必要。

再発性暴力

擬似コード

minSum(int[][] arr, int x, int y)
    return arr[x][y] + Math.min(minSum(arr,x+1,y),minSum(arr,x,y+1));

アイデアは非常に単純で、全体の問題の解決策は離れて正しい道を形成し、より小さなを下る現在位置の値を高めることです。
XおよびYが増加し続ける、境界の最後の行をxまたはyが最後に到達したので、コードは以下のように書き換えられる達します。

minSum(int[][] arr, int x, int y)
    int sum=0;
    if(x==arr.length-1)//到达右下角只有一条路可走
        for i=y...arr.length-1
            sum+=arr[x][i]
        return sum;
    if(y==arr.length-1)//到达右下角只有一条路可走
        for i=x...arr.length-1
            sum+=arr[i][y]
        return sum;
    return arr[x][y] + Math.min(minSum(arr,x+1,y),minSum(arr,x,y+1));

Java実装

public class 数字矩阵的最小路径和 {
  public static int minSum(int[][] arr, int x, int y) {
    int sum = 0;
    if (x == arr.length - 1) {//到达右下角只有一条路可走
      for (int i = y; i < arr[0].length; i++) {
        sum += arr[x][i];
      }
      return sum;
    }
    if (y == arr[0].length - 1) {//到达右下角只有一条路可走
      for (int i = x; i < arr.length; i++) {
        sum += arr[i][y];
      }
      return sum;
    }
    return arr[x][y] + Math.min(minSum(arr, x + 1, y), minSum(arr, x, y + 1));
  }

  public static void main(String[] args) {
    System.out.println(minSum(new int[][]{
        {1, 3, 5, 9},
        {8, 1, 3, 4},
        {5, 0, 6, 1},
        {8, 8, 4, 0},
    },0,0));
  }
}

検索型メモリ

以前のアイデアに提示よると、あなたは計算値をキャッシュするために2次元配列を使用することができ、再帰関数のパラメータの変化を調査し、ほとんど考えず、マスター・ルーチンの後に書き換えることができ、非常に簡単です:

  public static int minSumMemory(int[][] arr, int x, int y, int[][] map) {
    int sum = 0;
    if (x == arr.length - 1) {//到达右下角只有一条路可走
      for (int i = y; i < arr[0].length; i++) {
        sum += arr[x][i];
      }
      map[x][y] = sum; // 缓存
      return sum;
    }
    if (y == arr[0].length - 1) {//到达右下角只有一条路可走
      for (int i = x; i < arr.length; i++) {
        sum += arr[i][y];
      }
      map[x][y] = sum;//缓存
      return sum;
    }
    //=====判断缓存,没有值再递归,保证一个xy组合只计算一次=====
    int v1 = map[x + 1][y];
    if (v1 == 0)
      v1 = minSum(arr, x + 1, y);
    int v2 = map[x][y + 1];
    if (v2 == 0)
      v2 = minSum(arr, x, y + 1);

    sum = arr[x][y] + Math.min(v1, v2);
    map[x][y] = sum; // 缓存
    return sum;
  }

一般的な暴力、再帰的指数では、メモリタイプは再帰的多項式(二次元行列が正方グレードである)レベルであり、その後、動的プログラミングは、効率を向上させることができないかもしれないが、何も使用再帰がないので、スタックオーバーフローのリスクを低減することができる書き換え。

ダイナミックプログラミング

ルーチンがキャッシュ値を取得するプロセスを研究するために提案される前に、現在の値がどのような値に依存再帰キャッシュされるように、逆のプロセスがオーバーダイナミック企画です。

この例では、キャッシュテーブルは、2次元配列であり、我々は最終的にこの点と右下隅に到達する最小のパスであるすべてのポイント値を記入しなければなりません。再帰的トップダウン、ボトムアップアクションが右下隅に一つだけのパスにこれらの点として、xからyは境界開始、すなわち最下行と右端の列を開始し調整することができ、自然に解決することができます。

            14
            5
            1
20  12  4   0

そして、自分自身の価値の真ん中に記入すること=以下と、最終的な答えである少数の権利、0-0位置の最終的な計算に+非常に簡単です:

12  11  13  14
16  8   8   5
12  7   7   1
20  12  4   0

コードを見てみましょう:

  public static int dp1(int[][] arr) {
    final int rows = arr.length;
    final int cols = arr[0].length;
    int[][] dp = new int[rows][cols];
    dp[rows - 1][cols - 1] = arr[rows - 1][cols - 1];
    //打表:最右一列
    for (int i = rows - 2; i >= 0; i--) {
      dp[i][cols - 1] = arr[i][cols - 1] + dp[i + 1][cols - 1];
    }
    //打表:最后一行
    for (int i = cols - 2; i >= 0; i--) {
      dp[rows - 1][i] = arr[rows - 1][i] + dp[rows - 1][i + 1];
    }
    for (int i = rows - 2; i >= 0; i--) {
      for (int j = cols - 2; j >= 0; j--) {
        dp[i][j] = arr[i][j]+Math.min(dp[i+1][j],dp[i][j+1]);
      }
    }
    return dp[0][0];
  }

DP 2次元テーブル、最小のパスを復元することができます。

書き込み絵は、ここで説明しました

このプロセスは、トップがうまく対応する番号を見つけるために、元の配列へのパス上で、右下隅に左から最小の順序を見つけることです。

空間圧縮方法のみ1次元配列DP

本実施形態は、単に予約なしパスと最小値、要求される出力パス、中間処理を戻します。ライン上に被覆された各行について計算再帰値を格納する一次元アレイを考える、それの最終的な図で0-0の値を有します。

本実施形態では、行と列の数が同じため、ここでそれは、あなたが配列のサイズとして行または列の数を選択することができない場合は、配列のサイズは、可能である、当然のことながら、列をなして、それに応じて推進力を変更します事前に促進するか、または列単位(複数の列)にするモード(複数の行の数)。

私たちはこのプロセスを説明してみましょう:

  1. dp=new int[4];初期値はすべて0です
  2. 更新すべきソースデータ[20 12 4 0]、この時間DP [I](最終行、i)と最小パス端からマトリックスを表します。
  3. 開始今すぐ更新は、[I]は最後の要素を更新するチャネル行列の最小の代表的な(最後から二番目の線、i)は、終点から、DPせdp[3]=arr[倒数2行][最后一列]+dp[3];、この時点で、DPなる[20 12 4 1];更新DP [2]: dp[2] = arr[倒数第二行][倒数第二列]+min(4,1)。、4 値以下であります1は正しい値であるので、あなたは徐々に更新することができます
  4. 最初の行の処理を繰り返し、ステップ3、戻りDP [0]

コード:

  /**
   * 空间压缩优化
   * @param arr
   * @return
   */
  public static int dp2(int[][] arr) {
    final int rows = arr.length;
    final int cols = arr[0].length;
    int N = 0;
    if (rows>=cols){
      N = cols;
    }
    int[] dp = new int[N];
    dp[N-1] = arr[rows - 1][N - 1];
    //打表:第一次更新
    for (int i = N - 2; i >= 0; i--) {
      dp[i] = arr[rows-1][i] + dp[i + 1];
    }
    // 行
    for (int i = rows-2; i >=0 ; i--) {
      dp[N-1]=arr[i][N-1]+dp[N-1];
      for (int j = N - 2; j >= 0; j--) {
        dp[j] = arr[i][j] + Math.min(dp[j],dp[j + 1]);
      }
      // Util.print(dp);
    }
    return dp[0];
  }

概要

最適化ルーチンのこの例では、確かに多くのスペースを節約し、アレイロールオーバーの方法により、二次元の動的プログラミングテーブルのタイトルのほとんどすべてのニーズに適用することができます。

しかし、空間圧縮方式では限界、最適なソリューションを元に戻すことはできません特定のパスを持っています。

参考図書「プログラマーコード面接ガイド。」

公開された127元の記事 ・が 97個のように勝っ ビュー310 000 +

おすすめ

転載: blog.csdn.net/zhengwei223/article/details/78763904