2016-08-20 30 views
5

當我說高效時,我的意思是代碼不是cpu密集型的。獲取和存儲最短路徑的有效方式

問題: 我有一個塊的領域。像下面的圖片中:

Image of a 10 by 10 field of blocks

這些塊的每一個代表一個自制Block類的一個實例。該塊類有一個List<Block> neighBours,其中存儲該塊的鄰居。因此,圖像中的每個區塊都知道它旁邊有哪些區塊。

我想要做的是從這張圖片中選取任意一個塊,然後計算這個塊有多少「步長」。例如,如果我選擇左上角的塊,我想要有一個Map<Block, Integer>表示每個塊離開拾取塊有多少「步長」。就像這樣:

Image of a 10 by 10 field of blocks with numbers in it

現在你說「只是存儲它的位置的X和Y的塊類和計算差值X +差異Y」,這是行不通的,因爲現場可以有間隙(前像下面的圖片它們之間以紅色表示):

Image of a 10 by 10 field of blocks with numbers in it

正如你可能會注意到,旁邊是前4步之遙的差距塊,現在是6步之遙。因此,通過使用利用鄰居信息的遞歸算法(我假設)獲得其他塊的多少步驟的最佳方式。我自己無法提高效率,我希望有人可能知道一些效果很好的東西。

我遇到的幾個問題是,因爲所有的塊都知道它們的鄰居,所以遞歸算法會在第一個和第二個塊之間來回走動。或者當在11x11字段上使用算法時,有3284個方法調用,這對於11x11字段而言似乎太高。

問: 所以我的問題是:什麼是有效的方式,使用什麼樣的鄰居每塊有知識,讓每塊多少步走是。

代碼: 這是當前代碼,我incase任何人都想看到它。

public class Block 
{ 
    List<Block> neighBours; 
    public Block(List<Block> neighBours) 
    { 
     this.neighBours = neighBours; 
    } 
    public Map<Block, Integer> getStepsAway() 
    { 
     Map<Block, Integer> path = new HashMap<Block, Integer>(); 
     getPaths(path, 0, 100); 
     return path; 
    } 
    public void getPaths(Map<Block, Integer> path, int pathNumber, int maxPathNumber) 
    {   
     if(pathNumber <= maxPathNumber) 
     { 
      for(Block block : neighBours) 
      { 
       Integer thePathNumber = path.get(block); 
       if(thePathNumber != null) 
       { 
        if(pathNumber < thePathNumber) 
        { 
         path.put(block, pathNumber); 
         block.getPaths(path, pathNumber + 1, maxPathNumber); 
        } 
       } 
       else 
       { 
        path.put(block, pathNumber); 
        block.getPaths(path, pathNumber + 1, maxPathNumber); 
       } 
      } 
     } 
    } 
} 
+1

您的問題,看起來有點像尋路的洞察力。你也許可以看看A *算法。 – Nico

+0

這個[page](http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html)完全描述了你的問題,空白空間類似於該頁面中討論的障礙。 – SomeDude

+0

檢查我的答案,這會比使用A *多次更好的性能,並且實現起來非常簡單 – Dici

回答

5

遞歸算法註定要在大網格上失敗。 Java不是爲深遞歸而設計的,並且只能在StackOverflowException失敗之前承受幾千次遞歸調用。對於Java中的大型尋路問題,只有迭代解決方案纔是合理的方法。

當然,您可以使用經典的尋路算法,如A *,但您必須將其應用於每個單元格,這將非常昂貴。

事實上,你的問題有點特別,你想計算的最小距離到所有細胞,而不僅僅是一個。因此,你可以用更聰明的方式做到這一點。你的問題的

一個特性是給出AB,如果從AB最小的路徑包含C那麼這個路徑也從最小的到ACCB。這就是我的直覺告訴我的,但在實施我的建議之前需要證明這一點。

我提出的算法是有效的,使用O(n)內存,並具有O(n^2)運行的複雜性(因爲你需要設置陣列在這許多細胞不能更快):

  • 開始你的第一個點,並設置所有有效鄰居的距離爲1。這樣做,您將記錄邊界,這是距離第一個單元格的距離爲1的所有單元格。
  • 然後,您遍歷邊界並將所有尚未分配距離的鄰居分配給距離爲2。距離爲2的所有單元格將成爲您的新邊框。
  • 迭代,直到邊界爲空

下面是一個完整的工作方案。該代碼可以以各種方式使用初始化和打印對象和基本整數矩陣更方便的方法得到改善,但你的想法:

public class Solution { 
    public enum Cell { FREE, BLOCKED } 

    // assuming cells is a rectangular array with non-empty columns 
    public static int[][] distances(Cell[][] cells, ArrayCoordinate startingPoint) { 
     int[][] distances = new int[cells.length][cells[0].length]; 
     // -1 will mean that the cell is unreachable from the startingPoint 
     for (int i = 0; i < cells.length; i++) { 
      for (int j = 0; j < cells[0].length; j++) { 
       distances[i][j] = -1; 
      } 
     } 
     distances[startingPoint.i][startingPoint.j] = 0; 

     Set<ArrayCoordinate> border = startingPoint.validNeighbours(cells); 
     for (int currentDistance = 1; !border.isEmpty(); currentDistance++) { 
      Set<ArrayCoordinate> newBorder = new HashSet<>(); 
      for (ArrayCoordinate coord : border) { 
       distances[coord.i][coord.j] = currentDistance; 

       for (ArrayCoordinate neighbour : coord.validNeighbours(cells)) { 
        if (distances[neighbour.i][neighbour.j] < 0) { 
         newBorder.add(neighbour); 
        } 
       } 
      } 
      border = newBorder; 
     } 

     return distances; 
    } 

    private static class ArrayCoordinate { 
     public ArrayCoordinate(int i, int j) { 
      if (i < 0 || j < 0) throw new IllegalArgumentException("Array coordinates must be positive"); 
      this.i = i; 
      this.j = j; 
     } 

     public final int i, j; 

     public Set<ArrayCoordinate> validNeighbours(Cell[][] cells) { 
      Set<ArrayCoordinate> neighbours = new HashSet<>(); 

      // inlining for not doing extra work in a loop iterating over (-1, 1) x (-1, 1). If diagonals are allowed 
      // then switch for using a loop 
      addIfValid(cells, neighbours, 1, 0); 
      addIfValid(cells, neighbours, -1, 0); 
      addIfValid(cells, neighbours, 0, 1); 
      addIfValid(cells, neighbours, 0, -1); 

      return neighbours; 
     } 

     private void addIfValid(Cell[][] cells, Set<ArrayCoordinate> neighbours, int dx, int dy) { 
      int x = i + dx, y = j + dy; 
      if (0 <= x && 0 <= y && x < cells.length && y < cells[0].length && cells[x][y] == Cell.FREE) { 
       neighbours.add(new ArrayCoordinate(i + dx, j + dy)); 
      } 
     } 

     @Override 
     public boolean equals(Object o) { 
      if (this == o) return true; 
      if (o == null || getClass() != o.getClass()) return false; 

      ArrayCoordinate point = (ArrayCoordinate) o; 

      if (i != point.i) return false; 
      if (j != point.j) return false; 

      return true; 
     } 

     @Override 
     public int hashCode() { 
      int result = i; 
      result = 31 * result + j; 
      return result; 
     } 
    } 

    public static void main(String[] args) { 
     int n = 11, m = 5; 

     Cell[][] cells = new Cell[n][m]; 
     cells[1][1] = Cell.BLOCKED; 
     cells[1][2] = Cell.BLOCKED; 
     cells[2][1] = Cell.BLOCKED; 

     ArrayCoordinate startingPoint = new ArrayCoordinate(5, 2); 

     System.out.println("Initial matrix:"); 
     for (int i = 0; i < cells.length; i++) { 
      for (int j = 0; j < cells[0].length; j++) { 
       if (cells[i][j] == null) { 
        cells[i][j] = Cell.FREE; 
       } 
       if (startingPoint.i == i && startingPoint.j == j) { 
        System.out.print("S "); 
       } else { 
        System.out.print(cells[i][j] == Cell.FREE ? ". " : "X "); 
       } 
      } 
      System.out.println(); 
     } 

     int[][] distances = distances(cells, startingPoint); 
     System.out.println("\nDistances from starting point:"); 
     for (int i = 0; i < distances.length; i++) { 
      for (int j = 0; j < distances[0].length; j++) { 
       System.out.print((distances[i][j] < 0 ? "X" : distances[i][j]) + " "); 
      } 
      System.out.println(); 
     } 
    } 
} 

輸出:

Initial matrix: 
. . . . . 
. X X . . 
. X . . . 
. . . . . 
. . . . . 
. . S . . 
. . . . . 
. . . . . 
. . . . . 
. . . . . 
. . . . . 

Distances from starting point: 
7 8 7 6 7 
6 X X 5 6 
5 X 3 4 5 
4 3 2 3 4 
3 2 1 2 3 
2 1 0 1 2 
3 2 1 2 3 
4 3 2 3 4 
5 4 3 4 5 
6 5 4 5 6 
7 6 5 6 7 

獎金

當我在我的Java解決方案中看到所有這些樣板時,我幾乎哭了起來,所以我在Scala中編寫了一個更短的(可能效率稍低)的版本:

object ScalaSolution { 
    sealed abstract class Cell 
    object Free extends Cell 
    object Blocked extends Cell 

    // assuming cells is a rectangular array with non-empty columns 
    def distances(cells: Array[Array[Cell]], startingPoint: (Int, Int)) = { 
    // -1 will mean that the cell is unreachable from the startingPoint 
    val distances = Array.fill[Int](cells.length, cells(0).length)(-1) 
    distances(startingPoint._1)(startingPoint._2) = 0 

    var (currentDistance, border) = (1, validNeighbours(cells, startingPoint)) 
    while (border.nonEmpty) { 
     border.foreach { case (i, j) => distances(i)(j) = currentDistance } 
     border = border.flatMap(validNeighbours(cells, _)).filter { case (i, j) => distances(i)(j) < 0 } 
     currentDistance += 1 
    } 

    distances 
    } 

    private def validNeighbours(cells: Array[Array[Cell]], startingPoint: (Int, Int)) = { 
    // inlining for not doing extra work in a for yield iterating over (-1, 1) x (-1, 1). If diagonals are allowed 
    // then switch for using a for yield 
    Set(neighbourIfValid(cells, startingPoint, (1, 0)), 
     neighbourIfValid(cells, startingPoint, (-1, 0)), 
     neighbourIfValid(cells, startingPoint, (0, 1)), 
     neighbourIfValid(cells, startingPoint, (0, -1))) 
     .flatten 
    } 

    private def neighbourIfValid(cells: Array[Array[Cell]], origin: (Int, Int), delta: (Int, Int)) = { 
    val (x, y) = (origin._1 + delta._1, origin._2 + delta._2) 
    if (0 <= x && 0 <= y && x < cells.length && y < cells(0).length && cells(x)(y) == Free) { 
     Some(x, y) 
    } else None 
    } 

    def main (args: Array[String]): Unit = { 
    val (n, m) = (11, 5) 

    val cells: Array[Array[Cell]] = Array.fill(n, m)(Free) 
    cells(1)(1) = Blocked 
    cells(1)(2) = Blocked 
    cells(2)(1) = Blocked 

    val startingPoint = (5, 2) 
    println("Initial matrix:") 
    printMatrix(cells)((i, j, value) => if ((i, j) == startingPoint) "S" else if (value == Free) "." else "X") 

    val distancesMatrix = distances(cells, startingPoint) 
    println("\nDistances from starting point:") 
    printMatrix(distancesMatrix)((i, j, value) => if (value < 0) "X" else value.toString) 
    } 

    private def printMatrix[T](matrix: Array[Array[T]])(formatter: (Int, Int, T) => String) = { 
    for (i <- 0 until matrix.length) { 
     for (j <- 0 until matrix(0).length) { 
     print(formatter(i, j, matrix(i)(j)) + " ") 
     } 
     println() 
    } 
    } 
} 
+1

謝謝您的回答,這正是我想要的!我已經將它應用於我的問題,它工作。然而,我確實有一個問題,我一直無法弄清楚。我將它應用於一個11x11矩陣(沒有阻塞單元),做了起始點(5,5)並在'newBorder.add(鄰居);'部分放置了一個計數器。由於某種原因,鄰居添加的次數爲216.不應該超過總單元的數量嗎? – JohnCake

+0

@JohnCake這個算法應該精確地遍歷每個單元格一次。例如,您可以在Gist(https://gist.github.com/)上分享代碼嗎? – Dici

+0

@JohnCake只是試了一下mysellf,得到了同樣的東西。我必須從中理解它 – Dici

1

我相信有一個DP(動態編程)解決這個問題的方法,看下面的代碼this。我知道這是一個尋找所有可能的路徑到一個細胞,但它可以對您的病情給予有關「空白」或「牆壁」

#include <iostream> 
using namespace std; 

// Returns count of possible paths to reach cell at row number m and column 
// number n from the topmost leftmost cell (cell at 1, 1) 
int numberOfPaths(int m, int n) 
{ 
    // Create a 2D table to store results of subproblems 
    int count[m][n]; 

    // Count of paths to reach any cell in first column is 1 
    for (int i = 0; i < m; i++) 
     count[i][0] = 1; 

    // Count of paths to reach any cell in first column is 1 
    for (int j = 0; j < n; j++) 
     count[0][j] = 1; 

    // Calculate count of paths for other cells in bottom-up manner using 
    // the recursive solution 
    for (int i = 1; i < m; i++) 
    { 
     for (int j = 1; j < n; j++) 

      // By uncommenting the last part the code calculatest he total 
      // possible paths if the diagonal Movements are allowed 
      count[i][j] = count[i-1][j] + count[i][j-1]; //+ count[i-1][j-1]; 

    } 
    return count[m-1][n-1]; 
} 
+0

我想我同意它可以被看作DP問題,但障礙物並不那麼容易,因爲沒有明智的方式來迭代矩陣。我也認爲更新公式也應該修改,可能會包含一個'Math.min'調用以及一些邏輯確定值是否應該增加或減少與每個鄰居相比。也許我只是悲觀,但我認爲解決它作爲DP可能比你一開始想的要棘手。 – Dici