A* 寻路算法
的讲解,这里有介绍比较清晰的文章:
A* Pathfinding for Beginners
首先引入两个集合,一个公式:
OpenList:可到达的格子
CloseList:已经走过的格子
F = G + H
- 其中,每一个格子都有三个属性值:
F,G,H
G
:从起点走到当前格子的成本,已经花费了多少步
H
:不考虑障碍的情况下,从当前格子走到目标格子的距离,离目标还有多远
F
:G和H的综合评估,也就是从起点到达当前格子,再从当前格子到达目标格子的总步数
算法执行的大致步骤如下:
1. 从OpenList中找出F值最小的Grid,作为当前我们需要走的路径格子
2. 于是我们从openList中移除此Grid,并加入到CloseList中
3. 寻找当前节点的四周邻居节点,如果是可以走的路径(不越界,非墙,不在OpenList,CloseList中),则设置当前节点为其父节点并将其加入到OpenList中
4. 如果OpenList中已经存在包含了终点格子,则直接返回
5. OpenList元素个数大于0,则继续1步骤
下面是A*算法的完整实现,并带有测试用例:
/**
* A*算法(启发式搜索):
* 首先引入两个集合,一个公式:
* OpenList:可到达的格子
* CloseList:已到达的格子
* F = G + H
*
* 每一个格子都有三个属性值:F,G,H
* G:从起点走到当前格子的成本, **已经花费了多少步**
* H:不考虑障碍的情况下,从当前格子走到目标格子的距离,**离目标还有多远**
* F:G和H的综合评估,也就是从起点到达当前格子,**再从当前格子到达目标格子的总步数**
*
*/
public class AStarPathSearching {
public final static int[][] MAZE = {
{0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 1, 0, 0, 0},
{0, 0, 0, 1, 0, 0, 0},
{0, 0, 0, 1, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0}
};
private static class Grid {
int x, y;
int f, g, h;
Grid parent;
Grid(int x, int y) {
this.x = x;
this.y = y;
}
void initGrid(Grid parent, Grid end) {
this.parent = parent;
this.g = (parent == null ? 1 : parent.g + 1);
this.h = Math.abs(this.x - end.x)
+ Math.abs(this.y - end.y);
this.f = this.g + this.h;
}
}
/**
* 寻路算法主方法
*
* @param start
* @param end
* @return
*/
public static Grid search(Grid start, Grid end) {
// 可到达的格子
List<Grid> openList = new ArrayList<>();
// 已到达的格子
List<Grid> closeList = new ArrayList<>();
openList.add(start);
while (openList.size() > 0) {
// 找到F值最小的格子,作为当前走的路径格子
// 从OpenList中移除,添加进CloseList中
Grid curGrid = findMinGrid(openList);
openList.remove(curGrid);
closeList.add(curGrid);
// 找到可达的邻居格子
List<Grid> neighbors = findNeighbors(curGrid, openList, closeList);
for (Grid grid : neighbors) {
if (!openList.contains(grid)) {
// 初始化父节点信息,以及计算F,G,H值
grid.initGrid(curGrid, end);
openList.add(grid);
}
}
// 如果OpenList中存在终点,那么直接返回
Grid endGrid = null;
if((endGrid = containGrid(openList, end.x, end.y)) != null) {
return endGrid;
}
}
return null;
}
/**
* 在OpenList中寻找F值最小的Grid
*
* @param openList
* @return
*/
private static Grid findMinGrid(List<Grid> openList) {
Grid minGrid = openList.get(0);
for (Grid grid : openList) {
if (grid.f < minGrid.f) {
minGrid = grid;
break;
}
}
return minGrid;
}
private static List<Grid> findNeighbors(Grid curGrid, List<Grid> openList, List<Grid> closeList) {
List<Grid> gridList = new ArrayList<>();
if (isValidGrid(curGrid.x, curGrid.y+1, openList, closeList)) {
gridList.add(new Grid(curGrid.x, curGrid.y+1));
}
if (isValidGrid(curGrid.x+1, curGrid.y, openList, closeList)) {
gridList.add(new Grid(curGrid.x+1, curGrid.y));
}
if (isValidGrid(curGrid.x, curGrid.y-1, openList, closeList)) {
gridList.add(new Grid(curGrid.x, curGrid.y-1));
}
if (isValidGrid(curGrid.x-1, curGrid.y, openList, closeList)) {
gridList.add(new Grid(curGrid.x-1, curGrid.y));
}
return gridList;
}
private static boolean isValidGrid(int x, int y, List<Grid> openList, List<Grid> closeList) {
// 越界
if (x < 0 || y < 0 || x >= MAZE.length || y >= MAZE[0].length) {
return false;
}
// 围墙
if (MAZE[x][y] == 1) {
return false;
}
if (containGrid(openList, x, y) != null || containGrid(closeList, x, y) != null) {
return false;
}
return true;
}
/**
* 判断gridList中是否存在 Grid(x, y),
* 如果存在则直接返回
* 否则返回 null
* @param gridList
* @param x
* @param y
* @return
*/
private static Grid containGrid(List<Grid> gridList, int x, int y) {
for(Grid grid : gridList) {
if(grid.x == x && grid.y == y) {
return grid;
}
}
return null;
}
public static void main(String[] args) {
Grid start = new Grid(2, 1);
Grid end = new Grid(2, 5);
Grid result = search(start, end);
List<Grid> path = new ArrayList<>();
while(result != null) {
path.add(new Grid(result.x, result.y));
result = result.parent;
}
for(int i = 0 ; i < MAZE.length ; i++) {
for(int j = 0 ; j < MAZE[i].length ; j++) {
if(MAZE[i][j] == 1) {
System.out.print("1 ");
} else if(containGrid(path, i, j) != null) {
System.out.print("* ");
} else {
System.out.print("0 ");
}
}
System.out.println();
}
}
}
实际上,这个算法有个地方可以优化;我们看到findMinGrid
函数,它是找到集合中F值最小
的方格,我们每次调用它都得遍历整个集合,算法是线性增加的,并且随着路径的扩大,这个集合的大小不断扩大,算法效率会很低,搜索时间很令人担忧
。
既然我们每次都取F值最小的元素,那么,可以想到,使用一个最小堆
来存储这些元素。这样,算法时间复杂度呈2的指数级下降;