この問題が発生しました-特定のポイントでの標高を表す数字がグリッドに表示されます。グリッドの各ボックスから、北、南、東、西に移動できますが、移動するエリアの標高が現在のエリアの標高よりも低い場合、つまり下降のみが可能です。マップ上のどこからでも開始でき、訪問したボックスの数で測定して、可能な限り最長の経路をたどる出発点を探しています。また、同じ長さのパスが複数ある場合は、垂直方向の降下が最も急なパス、つまり開始標高と終了標高の差が最も大きいものを使用します。元グリッド:
4 8 7 3
2 5 9 3
6 3 2 5
4 4 1 6
この特定のマップでは、最長の下りパスはlength = 5で、9-5-3-2-1です。
長さ5の別のパスがあります。8-5-3-2-1です。 9から1(8)へのドロップは、8から1(7)よりも急なドロップです。)WAPは、最長の(そして次に急な)パスを見つけます。
私はFloyd Warshalls、DAG +トポロジカルソートを使用して、DFSについて考えようとしましたが、DPについてはさまざまなことを考えましたが、考える方法を見つけることができません。任意のポインタ+アプローチのための単純な疑似コードが役立つでしょう。
この問題に関するいくつかの観察。
与えられた制約のある最長パスを探しているので、最短パスを見つけるように設計されているA *またはFloyd-Warshallを使用することは役に立ちません。
極端なケースでは、パスにグリッドのすべてのフィールドを含めることができます。例:
_1 2 3
8 9 4
7 6 5
_
(ここでは、パスがらせんを形成していますが、他の図も可能です。)
したがって、理想的なアルゴリズムはO(n)よりも優れているわけではありません。これは、各フィールドを一度訪問するのと同じ複雑さでもあります。
フィールドFとその周囲のフィールドN、E、S、Wが存在する限り、パスの長さL(F)
を次のように計算できます。
_ ⎧ 1 + F(M) if M exists
L(F) = ⎨
⎩ 0 otherwise
_
ここで_M ∈ {N, E, S, W}
_はValue(M) < Value(F)
とF(M) = max({F(X) | X ∈ {N, E, S, W}})
になります。擬似コードとして:
_def L(F):
lower = {X for X in {N, E, S, W} if X.value < F.value}
if lower.is_empty:
return 0
return 1 + max({ L(X) for X in lower})
_
正しさの証明は、読者への課題として残されます。
関数L(F)
は、メモ化を使用して計算できます。つまり、L(F)
のキャッシュを構築して、各フィールドが1回だけ計算されるようにします。最初は、各計算に他のフィールドの計算も含まれる場合があり、最悪の場合はすべてのフィールドが含まれます。したがって、私たちのメモ化されたL(F)
は複雑さを持っていますO(n) to O(1)は、キャッシュにすでに必要な値が含まれているかどうかによって異なります。しかし、キャッシュ全体の複雑さの平均はO(1)です(各値は1回だけ計算され、一定の回数だけ要求されます)。
したがって、すべてのフィールドを反復処理してそのフィールドのパス長を計算すると、この反復はO(n)になります。したがって、メモ化を伴うこのアルゴリズムは理想的です!
最初に計算する必要があるフィールドがわからないため、動的プログラミングを適用できません。ええと、すべてのフィールドを値で並べ替えて、最初に最低の計算を開始することができますが、結果として、遅延してキャッシュを構築するよりもコストが高くなり、はるかに複雑になります。
L(F)を計算している間、何らかのフィールドを含むパスと現在最も長いパスを追跡することもできます。これらすべてが複雑さに影響することはありません。
一緒に、次のアプローチが現れます:
M
ネイバーが選択されている場合、次のフィールドはM
に設定されます。複数のフィールドにM
のプロパティがある場合、これは問題になる可能性があります。このマトリックスでは:
_1 2 3
4 9 8
5 8 9
_
_9
_ sから始めて、どの_8
_を理想的なパスの一部にする必要があるかはすぐにはわかりません。すべての可能なパスを追跡するためにこれを実装するか(複雑さに影響を与えません)、または最急降下の部分パスを選択します。これは、フリーではなくO(n)の両方のパスを歩く必要があります。これを回避するには、最大勾配を各フィールドの追加プロパティとしてメモします。
ちなみに、この例は、2つのソリューション_9-8-3-2-1
_がある場合を示しています。
疑似コード:
_record Field {
value : Int
length : Count? = null
next : Field? = null
slope : Int? = null
}
get-neighbours(f : Field): Set[Field] {
...
}
get-length(f : Field): Int {
if f.length != null {
return f.length
}
let next : Collection[Field] = get-neighbours(f)
.where(x -> x.value < f.value) // must be smaller
.max-by(x -> get-length(x)) // must have longest partial path
.max-by(x -> x.slope) // prefer steepest path
if next.is_empty {
f.length = 0
return f.length
}
let next = next.first // pick any field that satisfies all properties
f.next = next
f.slope = next.slope ?? f.value - next.value
f.length = 1 + next.length
return f.length
}
get-path(f: Field): Field generator {
yield f
if f.next != null {
yield from get-path(f.next)
}
}
let matrix : Array[height, Array[width, Field]] = {
{ Field(1), ... },
...,
}
// evaluate length for each field in matrix
// and keep track of longest paths
let mut longest = List()
let mut length = 0
for f in matrix {
let l = get-lengh(f)
if l > length {
length = get-length(f)
longest = List(f)
}
else if l == length {
longest.add(f)
}
}
// print out longest paths
for start in longest {
println(List(get-path(start)))
}
_
動的プログラミングのアプローチが機能するはずです。
各セルについて:
そこから最適な(最長で急な勾配の)下りパスを見つける問題を、より小さな隣接セルから最適パスを見つけ、次にその隣接セルにステップを追加して、セルから最適パスを取得する問題を減らします。質問。
Memo(r)ize 途中の各セルからの最適なパス。
実際には明示的なパスを保存せず、パスの次のセルのみを保存します(そのようにして、パスはそこにあり、暗黙的に)。
正しく実行すれば、それはマトリックスのサイズに比例して実行されるはずです...もちろん問題を誤解していない限り。
コードは次のようになります。
public class BestDynamicDescendingPathFinder {
public static int[][] example = new int[][]{{4, 8, 7, 3},{2, 5, 9, 3},{6, 3, 2, 5},{4, 4, 1, 6}};
public static void main(String[] args)
{
BestDynamicDescendingPathFinder Finder = new BestDynamicDescendingPathFinder(example);
System.out.println("Best overall: " + Arrays.toString(Finder.find()));
System.out.println("Best starting from some other cell: " + Arrays.toString(Finder.unfoldBestPathFromCell(3, 3)));
}
private int[][] matrix;
private PathInformation[][] informationForBestPathFromCellMemory;
public BestDynamicDescendingPathFinder(int[][] aMatrix)
{
informationForBestPathFromCellMemory = new PathInformation[aMatrix.length][];
matrix = new int[aMatrix.length][];
for(int i = 0; i < aMatrix.length; i++)
{
informationForBestPathFromCellMemory[i] = new PathInformation[aMatrix[i].length];
matrix[i] = new int[aMatrix[i].length];
for(int j = 0; j < aMatrix[i].length; j++)
{
matrix[i][j] = aMatrix[i][j];
}
}
}
// find the best path by getting the best starting cell and unfolding the information for it
public int[] find()
{
int currentBestStartingCellColumn = 0;
int currentBestStartingCellRow = 0;
for(int i = 0; i < matrix.length; i++)
{
for(int j = 0; j < matrix[i].length; j++)
{
if(getInformationForBestPathFromCell(i, j).compareTo(getInformationForBestPathFromCell(currentBestStartingCellColumn, currentBestStartingCellRow)) == 1){
currentBestStartingCellColumn = i;
currentBestStartingCellRow = j;
}
}
}
return unfoldBestPathFromCell(currentBestStartingCellColumn, currentBestStartingCellRow);
}
// unfold the best path (starting) from a cell by walking the PathInformation structures in memory
private int[] unfoldBestPathFromCell(int colNum, int rowNum)
{
PathInformation currentCellInformation = getInformationForBestPathFromCell(colNum, rowNum);
int[] path = new int[currentCellInformation.length];
path[0] = matrix[colNum][rowNum];
int idx = 1;
while(currentCellInformation.length > 1)
{
path[idx] = matrix[currentCellInformation.nextCellColumn][currentCellInformation.nextCellRow];
idx++;
currentCellInformation = getInformationForBestPathFromCell(currentCellInformation.nextCellColumn, currentCellInformation.nextCellRow);
}
return path;
}
// get the information for the best path (starting) from a cell: from memory if available or calculate otherwise
private PathInformation getInformationForBestPathFromCell(int colNum, int rowNum)
{
if(informationForBestPathFromCellMemory[colNum][rowNum] == null)
{
informationForBestPathFromCellMemory[colNum][rowNum] = calculateInformationForBestPathFromCell(colNum, rowNum);
}
return informationForBestPathFromCellMemory[colNum][rowNum];
}
// calculate the information for the best path (starting) from a cell by using the information for best paths from neighboring cells
private PathInformation calculateInformationForBestPathFromCell(int colNum, int rowNum)
{
List<PathInformation> possiblePathsFromCell = new ArrayList<PathInformation>();
if(colNum != 0 && matrix[colNum - 1][rowNum] < matrix[colNum][rowNum])
{
PathInformation p = getInformationForBestPathFromCell(colNum - 1, rowNum);
possiblePathsFromCell.add(new PathInformation(p.length + 1, matrix[colNum][rowNum], p.endValue, colNum - 1, rowNum));
}
if(colNum != matrix.length - 1 && matrix[colNum + 1][rowNum] < matrix[colNum][rowNum])
{
PathInformation p = getInformationForBestPathFromCell(colNum + 1, rowNum);
possiblePathsFromCell.add(new PathInformation(p.length + 1, matrix[colNum][rowNum], p.endValue, colNum + 1, rowNum));
}
if(rowNum != 0 && matrix[colNum][rowNum - 1] < matrix[colNum][rowNum])
{
PathInformation p = getInformationForBestPathFromCell(colNum, rowNum - 1);
possiblePathsFromCell.add(new PathInformation(p.length + 1, matrix[colNum][rowNum], p.endValue, colNum, rowNum - 1));
}
if(rowNum != matrix[colNum].length -1 && matrix[colNum][rowNum + 1] < matrix[colNum][rowNum])
{
PathInformation p = getInformationForBestPathFromCell(colNum, rowNum + 1);
possiblePathsFromCell.add(new PathInformation(p.length + 1, matrix[colNum][rowNum], p.endValue, colNum, rowNum + 1));
}
if(possiblePathsFromCell.isEmpty())
{
return new PathInformation(1, matrix[colNum][rowNum], matrix[colNum][rowNum], -1, -1);
}
return Collections.max(possiblePathsFromCell);
}
}
public class PathInformation implements Comparable<PathInformation>
{
int length;
int startValue;
int endValue;
int nextCellColumn;
int nextCellRow;
public PathInformation(int length, int startValue, int endValue, int nextCellColumn, int nextCellRow)
{
this.length = length;
this.startValue = startValue;
this.endValue = endValue;
this.nextCellColumn = nextCellColumn;
this.nextCellRow = nextCellRow;
}
@Override
public int compareTo(PathInformation other) {
if(this.length < other.length || (this.length == other.length && this.startValue - this.endValue < other.startValue - other.endValue)){
return -1;
}
if(this.length > other.length || (this.length == other.length && this.startValue - this.endValue > other.startValue - other.endValue)){
return 1;
}
return 0;
}
}