java – 获取和存储最短路径的有效方法

当我说效率时,我指的是不是cpu密集的代码.

问题:
我有一块街区.如下图所示:

这些块中的每一个都代表一个自制Block类的实例.该块类具有List< Block>邻居,存储块的邻居.因此,图像中的每个块都知道它旁边的块.

我想要做的是从这个图像中选择任何一个块,并计算这个块的“步数”.例如,如果我选择左上角的块,我想要一个Map< Block,Integer>表示每个块距离拾取块有多少“步”.像这样:

现在在您说“只是将它的位置X和Y存储在块类中并计算差异X差异Y”之前,这将无法工作,因为该字段之间可能存在间隙(用红色表示),如下图所示:

正如您可能会注意到的那样,距离前4步之间的距离旁边的区块现在距离6步之遥.因此,通过使用利用邻居信息的递归算法,获得其他块多少步的最佳方式(我推测).我不能自己做一个有效的,我希望有人可能知道一些运作良好的东西.

我遇到的几个问题是,因为所有块都知道它们的邻居,所以递归算法将在第一个和第二个块之间来回无限地来回.或者说,当在11×11字段上使用算法时,有3284个方法调用,对于11×11字段来说似乎太高了.

题:
所以我的问题是:什么是一种有效的方法,利用每个块的邻居知识,得到每个块有多少步.

码:
这是我想要看到它的当前代码.

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);
                }
            }
        }
    }
}
最佳答案
递归算法注定要在大型网格上失败. Java不是为深度递归而设计的,并且在使用StackOverflowException失败之前只能承受几千次递归调用.只有迭代解决方案才是Java中大型寻路问题的合理方法.

当然,您总是可以使用经典的寻路算法,例如A *,但是您必须为每个单元格应用它,这将非常昂贵.

实际上,在你想要计算到所有单元格的最小距离而不仅仅是一个单元格的意义上,你的问题有点特别.因此,您可以更聪明地完成它.

你的问题的一个属性是给定A和B,如果从A到B的最小路径包含C,那么这条路径从A到C以及从C到B也是最小的.这就是我的直觉告诉我的,但它需要在实施我的建议之前得到证实.

我建议的算法很有效,使用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()
    }
  }
}

转载注明原文:java – 获取和存储最短路径的有效方法 - 代码日志