「金鉱」という名前でよく知られている動的プログラミングの問題があります。 n x nグリッドがあり、その各セルには特定の値のコインが含まれています。左下から開始し、右、上、または斜め上および右にのみ移動できます。アルゴリズムの目標は、収集する金の量を最大化する鉱山の経路を決定することです。
ソリューションの例: http://www.ideserve.co.in/learn/gold-mine-problem
O(n ^ 2)時間を使用しながら、O(n)スペース以下)を使用した分割統治アプローチを使用してこの問題を解決することはできますか?
私の最初の疑いは、グリッドを四分区間に再帰的に分割し、それらの各四分区間で最適なパスを見つけて、結果を最終的にマージして、全体の中で最適なパスを取得できることです。 O(n)スペースのみを使用すると、各象限にパスを保存できます。各パスを通る最長パスが2n-2だからです。選択されなかった価値のあるポイントであり、そのアルゴリズムによって提供される答えは不正確になる傾向があります。破棄されたセルを記憶しようとして各マージステップですべてのセルを調べた場合(n ^ 2時間かかります)の場合、実行時間の反復関係の解は、マスター定理により、n ^ 2ではなくn ^ 2 log nになります。マージステップは、O(n log n)またはO(n)で実行する必要があります。
ここでのスペース要件は、時間要件よりもキッカーです。
答えがあったことがわかりました。
これを行うアルゴリズムは次のように機能します。
グリッドを四分円に分割します。右上の象限、次に左上、次に右下のDP値の解決を開始します。これを行うときは、他の象限に隣接するセルのDPデータのみを保存し、他のすべての作業領域を削除します。
その後、実際の再帰は左下の象限から始まります。この副問題は、より大きな問題と同じに見えます-象限を4つに分割し、同じことをもう一度行います。
サイズが2x2のグリッドになったら、通常はDPアプローチを使用して解きます。これは一定の時間です。そのグリッドを通るパスの取得が終了すると、境界セルをメモリに保持して選択できるため、次に入力する象限もわかります。さて、その象限を通過する経路を解いてから、最後の象限を解いてください。
ムーブセットの制約により、パスは3つの象限のみを通過します。したがって、実際に解決しているのは4つの問題ではなく3つのサブ問題だけです。このため、繰り返しがO(n ^ 2)に単純化されます。
T(n) = 3T(n/2) + O(n^2)
O(n ^ 2)は、境界のDPセルを取得するアルゴリズムの最初の部分です。 3T(n/2)は、3つの象限の解です。これは、Log_b(a)<cであるマスター定理のケースに属するため、T(n) = O(n ^ c)= O(n ^ 2)です。
宇宙の再発はこれです:
T(n)= T(n/2)+ n
これは、実行時間の繰り返しとして、マスター定理の同じケースに属します。使用される合計スペースはO(n)です。
グリッドを4つの象限に分割する分割統治法の問題があります。象限のソリューションは互いに独立していません。また、各象限の局所的に最適なサブソリューションを組み合わせても、最適なソリューションを見つけることはできません。
+---+---+
| C | D |
+---+---+
| A | B |
+---+---+
ここで、Dは独立して計算できます。ただし、CのソリューションはDに依存し、BはDに依存し、AはB、C、Dに依存します(依存関係は到達可能性によって決まります)。
象限の問題を解決しても、the最適パスが見つかるわけではありません。代わりに、最適なパスがその象限にどこに入るかわからないため、最適なパス各セルのを左端と下端で見つける必要があります。
正確な空間の複雑さは、計算する対象によって異なります。
この議論ではそれを避け、スペース要件をs(n)で示します。次に、象限の解法では、左と下のエッジのレコードにO(n・s(n))のスペースが必要です。
アルゴリズムの全体的なスペースの複雑さは、反復関係T(n) = 4・T(n/4)+ O(n・s(n))によって与えられます。これにより、 T(n)∈Θ(n・s(n)) s(n)のどちらの選択肢でも。
アルゴリズムを書き留めるのは非常に面倒なので、ここではそれを行いません。ただし、再帰ソルバーのシグネチャは次のようになります。
solve_quadrant(grid: int[n, n],
x_start: int, x_end: int,
y_start: int, y_end: int,
upper_Edge: Result[],
right_Edge: Result[])
-> (left_Edge: Result[], lower_Edge: Result[])
そして、トップレベルの呼び出しは次のようになります:
solve(grid: int[n, n]) -> Result {
left_Edge, lower_Edge = solve_quadrant(grid, 0, n, 0, n, {0, ..., 0}, {0, ..., 0})
return lower_Edge[0] // assuming bottom-left is at coordinate (0, 0)
}
分割統治アルゴリズムは、動的プログラミングソリューションと同じデータ依存関係を満たす必要があるため、空間の複雑さΘ(n・s(n))でDPソリューションに勝る可能性はほとんどありません。実際、「分割統治」アルゴとDPアルゴの複雑さは同じです。これは、同じことを同じ方法で行うためです。細分化の戦略のみが異なります。ただし、DPソリューションは多くのエッジ条件を考慮する必要がないため、プログラミングがはるかに簡単です。