2016-08-20 88 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