英文:
A* Path Finder (Java) is inefficient using a 1024 multidimentional array
问题
import java.util.List;
import java.util.ArrayList;
class AStarTwo {
private final List<Node> openList;
private final List<Node> closedList;
private final List<Node> calcPath;
private final int[][] collisionMap;
private Node currentNode;
private final int xstart;
private final int ystart;
private int xEnd, yEnd;
static class Node implements Comparable {
public Node parent;
public int x, y;
public double g;
public double h;
Node(Node parent, int xpos, int ypos, double g, double h) {
this.parent = parent;
this.x = xpos;
this.y = ypos;
this.g = g;
this.h = h;
}
@Override
public int compareTo(Object o) {
Node that = (Node) o;
return (int)((this.g + this.h) - (that.g + that.h));
}
}
public AStarTwo(int[][] collisionMap, int xstart, int ystart) {
this.openList = new ArrayList<>();
this.closedList = new ArrayList<>();
this.calcPath = new ArrayList<>();
this.collisionMap = collisionMap;
this.currentNode = new Node(null, xstart, ystart, 0, 0);
this.xstart = xstart;
this.ystart = ystart;
}
public List<Node> findPathTo(int xTo, int yTo) {
this.xEnd = xTo;
this.yEnd = yTo;
this.closedList.add(this.currentNode);
addNeigborsToOpenList();
while (this.currentNode.x != this.xEnd || this.currentNode.y != this.yEnd) {
if (this.openList.isEmpty()) {
return null;
}
this.currentNode = this.openList.get(0);
this.openList.remove(0);
this.closedList.add(this.currentNode);
addNeigborsToOpenList();
}
this.calcPath.add(0, this.currentNode);
while (this.currentNode.x != this.xstart || this.currentNode.y != this.ystart) {
this.currentNode = this.currentNode.parent;
this.calcPath.add(0, this.currentNode);
}
return this.calcPath;
}
private static boolean checkNeighbourHasBeenSearched(List<Node> array, Node node) {
return array.stream().anyMatch((n) -> (n.x == node.x && n.y == node.y));
}
private double distance(int dx, int dy) {
return Math.hypot(this.currentNode.x + dx - this.xEnd, this.currentNode.y + dy - this.yEnd);
}
private void addNeigborsToOpenList() {
Node node;
for (int x = -1; x <= 1; x++) {
for (int y = -1; y <= 1; y++) {
node = new Node(this.currentNode, this.currentNode.x + x, this.currentNode.y + y, this.currentNode.g, this.distance(x, y));
if ((x != 0 || y != 0)
&& this.currentNode.x + x >= 0 && this.currentNode.x + x < this.collisionMap[0].length
&& this.currentNode.y + y >= 0 && this.currentNode.y + y < this.collisionMap.length
&& this.collisionMap[this.currentNode.y + y][this.currentNode.x + x] != -1) {
if (!checkNeighbourHasBeenSearched(this.openList, node) && !checkNeighbourHasBeenSearched(this.closedList, node)) {
node.g = node.parent.g + 1.0;
node.g += collisionMap[this.currentNode.y + y][this.currentNode.x + x];
if (x != 0 && y != 0) {
node.g += .4;
}
this.openList.add(node);
}
}
}
}
}
public static void main(String[] args) {
int [][] maze = {
// maze data here
};
final int sizeOf = 20;
int[][] collisionMap = new int[sizeOf][sizeOf];
// Set collision map values here
AStarTwo as = new AStarTwo(maze, 9, 9);
List<Node> path = as.findPathTo(0,0);
if (path == null) {
System.out.println("Unable to reach target");
}
// Output image generation
BufferedImage img = new BufferedImage(sizeOf, sizeOf, BufferedImage.TYPE_INT_RGB);
// Set colors and fill image
File f = new File("aStarPath.png");
// Save image to file
}
}
英文:
I have the below code for a A* pathfinder, however it is taking upwards of 10 minutes to find a solution using a simple 1024 x 1024 array.
I had to comment out //Collections.sort(this.openList); as it was throwing a comparison method violates its general contract! error when running.
Is the algorithm correct and any idea why the bottleneck? Some people using C++ are getting a response time of 40ms, not 10+ mins!
When using the maze
array it does it in the blink of an eye, but thats using something like a 14x10 array, rather than 1024 from the collisionMap
.
Is the method flawed in some way? Or using the wrong data structures?
import java.util.List;
import javax.imageio.ImageIO;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.GraphicsConfiguration;
import java.awt.Paint;
import java.awt.image.BufferedImage;
import java.io.Console;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
class AStarTwo {
// Closed list, open list and calculatedPath lists
private final List<Node> openList;
private final List<Node> closedList;
private final List<Node> calcPath;
// Collision Map to store tha map in
private final int[][] collisionMap;
// Current node the program is executing
private Node currentNode;
// Define the start and end coords
private final int xstart;
private final int ystart;
private int xEnd, yEnd;
// Node class
static class Node implements Comparable {
public Node parent;
public int x, y;
public double g;
public double h;
Node(Node parent, int xpos, int ypos, double g, double h) {
this.parent = parent;
this.x = xpos;
this.y = ypos;
this.g = g;
this.h = h;
}
// Compare f value (g + h)
@Override
public int compareTo(Object o) {
Node that = (Node) o;
return (int)((this.g + this.h) - (that.g + that.h));
}
}
// construct and initialise
public AStarTwo(int[][] collisionMap, int xstart, int ystart) {
this.openList = new ArrayList<>();
this.closedList = new ArrayList<>();
this.calcPath = new ArrayList<>();
this.collisionMap = collisionMap;
this.currentNode = new Node(null, xstart, ystart, 0, 0);
this.xstart = xstart;
this.ystart = ystart;
}
// returns a List<> of nodes to target
public List<Node> findPathTo(int xTo, int yTo) {
this.xEnd = xTo;
this.yEnd = yTo;
// Add this to the closed list
this.closedList.add(this.currentNode);
// Add neighbours to openList for iteration
addNeigborsToOpenList();
// Whilst not at our target
while (this.currentNode.x != this.xEnd || this.currentNode.y != this.yEnd) {
// If nothing in the open list then return with null - handled in error message in main calling func
if (this.openList.isEmpty()) {
return null;
}
// get the lowest f value and add it to the closed list, f calculated when neighbours are sorted
this.currentNode = this.openList.get(0);
this.openList.remove(0);
this.closedList.add(this.currentNode);
addNeigborsToOpenList();
}
// add this node to the calculated path
this.calcPath.add(0, this.currentNode);
while (this.currentNode.x != this.xstart || this.currentNode.y != this.ystart) {
this.currentNode = this.currentNode.parent;
this.calcPath.add(0, this.currentNode);
}
return this.calcPath;
}
// Searches the current list for neighbouring nodes returns bool
private static boolean checkNeighbourHasBeenSearched(List<Node> array, Node node) {
return array.stream().anyMatch((n) -> (n.x == node.x && n.y == node.y));
}
// Calculate distance from current node to the target
private double distance(int dx, int dy) {
return Math.hypot(this.currentNode.x + dx - this.xEnd, this.currentNode.y + dy - this.yEnd); // return hypothenuse
}
// Add neighbouring nodes to the open list to iterate through next
@SuppressWarnings("unchecked")
private void addNeigborsToOpenList() {
Node node;
for (int x = -1; x <= 1; x++) {
for (int y = -1; y <= 1; y++) {
node = new Node(this.currentNode, this.currentNode.x + x, this.currentNode.y + y, this.currentNode.g, this.distance(x, y));
// if we are not on the current node
if ((x != 0 || y != 0)
&& this.currentNode.x + x >= 0 && this.currentNode.x + x < this.collisionMap[0].length // check collision map boundaries
&& this.currentNode.y + y >= 0 && this.currentNode.y + y < this.collisionMap.length
&& this.collisionMap[this.currentNode.y + y][this.currentNode.x + x] != -1) { // check if tile is walkable (-1)
// and finally check we haven't already searched the nodes
if(!checkNeighbourHasBeenSearched(this.openList, node) && !checkNeighbourHasBeenSearched(this.closedList, node)){
node.g = node.parent.g + 1.; // Horizontal/vertical cost = 1.0
node.g += collisionMap[this.currentNode.y + y][this.currentNode.x + x]; // add movement cost for this square
// Add diagonal movement cost sqrt(hor_cost² + vert_cost²) + 0.4
if (x != 0 && y != 0) {
node.g += .4;
}
// Add the node to the List<>
this.openList.add(node);
}
}
}
}
// sort in ascending order
//Collections.sort(this.openList);
}
public static void main(String[] args) {
int [][] maze =
{ {1,1,1,1,1,1,1,1,1,1,1,1,1},
{1,0,-1,0,-1,0,1,0,0,0,0,0,1},
{1,0,-1,0,0,0,1,0,1,1,1,0,1},
{1,0,0,0,-1,-1,-1,0,0,0,0,0,1},
{1,0,1,0,0,0,0,0,1,1,1,0,1},
{1,0,1,0,-1,-1,-1,0,1,0,0,0,-1},
{1,0,-1,0,-1,0,0,0,-1,-1,-1,0,-1},
{1,0,1,0,-1,-1,-1,0,1,0,-1,0,-1},
{1,0,0,0,0,0,0,0,0,0,1,0,1},
{1,1,1,1,1,1,1,1,1,1,1,1,1}
};
// Define the size of the grid
final int sizeOf = 20;
int[][] collisionMap = new int[sizeOf][];
for(int i=0;i < sizeOf; i++) {
// -1 = blocked
// 0+ = cost
collisionMap[i] = new int[sizeOf];
}
// set the value of the nodes
for (int k = 0; k < sizeOf; k++) {
for (int j = 0; j < sizeOf; j++) {
if(j == 30 && k < 100) {
collisionMap[k][j] = -1;
} else if (j == 50 && k > 230) {
collisionMap[k][j] = -1;
}else {
collisionMap[k][j] = 0;
}
}
}
AStarTwo as = new AStarTwo(maze, 9, 9);
List<Node> path = as.findPathTo(0,0);
if(path == null) {
System.out.println("Unable to reach target");
}
// create image buffer to write output to
BufferedImage img = new BufferedImage(sizeOf, sizeOf, BufferedImage.TYPE_INT_RGB);
// Set colours
int r = 255;
int g = 0;
int b = 0;
int colRed = (r << 16) | (g << 8) | b;
r = 0;
g = 255;
b = 0;
int colGreen = (r << 16) | (g << 8) | b;
r = 0;
g = 0;
b = 255;
int colBlue = (r << 16) | (g << 8) | b;
r = 255;
g = 255;
b = 255;
int colWhite = (r << 16) | (g << 8) | b;
int i = 0;
int j = 0;
if (path != null) {
path.forEach((n) -> {
System.out.print("[" + n.x + ", " + n.y + "] ");
maze[n.y][n.x] = 2;
});
for (int[] maze_row : maze) {
for (int maze_entry : maze_row) {
switch (maze_entry) {
// normal tile
case 0:
img.setRGB(j, i, colWhite);
break;
// final path
case 2:
img.setRGB(j, i, colBlue);
break;
// Object to avoid
case -1:
img.setRGB(j, i, colRed);
break;
// Any other value
default:
img.setRGB(j, i, colWhite);
}
j++;
}
// count j - reset as if it were a for loop
if(i != 12) {
j=0;
}
i++;
System.out.println();
}
}
// output file
File f = new File("aStarPath.png");
try {
ImageIO.write(img, "PNG", f);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("i: " + i + ", j: " + j);
}
}
答案1
得分: 0
我怀疑你的问题出在这一行:
return array.stream().anyMatch((n) -> (n.x == node.x && n.y == node.y));
这一行被调用大约O(n^2)次,并且将花费与数组大小成正比的时间(在最坏情况下,对于一个n乘n的迷宫,数组大小也将是O(n^2))。
你希望有一个更快的方法来执行这个测试。
例如:
- 使用一个集合来保存开放和关闭列表,而不是列表。
- 或者在节点结构中使用一个额外的字段来指示它是否在开放或关闭列表中。
英文:
I suspect your problem is this line:
return array.stream().anyMatch((n) -> (n.x == node.x && n.y == node.y));
which is called around O(n^2) times and will take time proportional to the size of the array (which will also be O(n^2) in the worst case for a n by n maze).
You want a faster method of performing this test.
For example:
- Use a set to hold the open and closed lists instead of list
- Or use an extra field in the node structure to indicate if it is in the open or closed list
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论