A星算法
A*搜寻算法,俗称A星算法。这是一种在图形平面上,有多个节点的路径,求出最低通过成本的算法。常用于游戏中的NPC的移动计算,或线上游戏的BOT的移动计算上。
该算法像Dijkstra算法一样,可以找到一条最短路径;也像BFS一样,进行启发式的搜索。
该算法像Dijkstra算法一样,可以找到一条最短路径;也像BFS一样,进行启发式的搜索。
Dijkstra的Java实现可以参照《Java实现Dijkstra算法》。
具体A星算法的理论部分已经在《A星算法——理论篇》一文中有详细说明,现直接上代码部分,该代码借助A星算法解决了一道acm题。题目如下:
援救行动
Problem
Angel被传说中神秘的邪恶的Moligpy人抓住了!他被关在一个迷宫中。迷宫的长、宽不超过200。
迷宫中有不可以越过的墙以及监狱的看守。
Angel的朋友带了一些救援队来到了迷宫中。他们的任务是:接近Angel。我们假设接近Angel就是到达Angel所在的位置。
假设移动需要1单位时间,杀死一个看守也需要1单位时间。到达一个格子以后,如果该格子有看守,则一定要杀死(否则会死
的很难看的……只见那个看守开了9倍狙镜……)。交给你的任务是,最少要多少单位时间,才能到达Angel所在的地方?
(只能向上、下、左、右4个方向移动)
Input
该题含有多组测试数据。
每组测试数据第一行二个整数n,m。表示迷宫的大小为n*m。
以后n行,每行m个时字符。其中“#”代表墙,“.”表示可以移动,“x”表示看守,“a”表示Angel,“r”表示救援队伍。
字母均为小写。
Output
一行,代表救出Angel的最短时间。
如果救援小组永远不能达到Angel处,则输出“Poor ANGEL has to stay in the prison all his life.”
Sample Input
7 8
#.#####.
#.a#..r.
#..#x...
..#..#.#
#...##..
.#......
........
Sample Output
13
Angel被传说中神秘的邪恶的Moligpy人抓住了!他被关在一个迷宫中。迷宫的长、宽不超过200。
迷宫中有不可以越过的墙以及监狱的看守。
Angel的朋友带了一些救援队来到了迷宫中。他们的任务是:接近Angel。我们假设接近Angel就是到达Angel所在的位置。
假设移动需要1单位时间,杀死一个看守也需要1单位时间。到达一个格子以后,如果该格子有看守,则一定要杀死(否则会死
的很难看的……只见那个看守开了9倍狙镜……)。交给你的任务是,最少要多少单位时间,才能到达Angel所在的地方?
(只能向上、下、左、右4个方向移动)
Input
该题含有多组测试数据。
每组测试数据第一行二个整数n,m。表示迷宫的大小为n*m。
以后n行,每行m个时字符。其中“#”代表墙,“.”表示可以移动,“x”表示看守,“a”表示Angel,“r”表示救援队伍。
字母均为小写。
Output
一行,代表救出Angel的最短时间。
如果救援小组永远不能达到Angel处,则输出“Poor ANGEL has to stay in the prison all his life.”
Sample Input
7 8
#.#####.
#.a#..r.
#..#x...
..#..#.#
#...##..
.#......
........
Sample Output
13
AStar类:
package com.sabrina.astar;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
public class AStar {
// 迷宫图
Point[][] maze;
// 起始节点
Point start;
// 终止节点
Point goal;
// 开启队列,用于存放待处理的节点
Queue<Point> openQueue = null;
// 关闭队列,用于存放已经处理过的节点
Queue<Point> closedQueue = null;
// 起始节点到某个节点的距离
int[][] FList = null;
// 某个节点到目的节点的距离
int[][] GList = null;
// 起始节点经过某个节点到目的节点的距离
int[][] HList = null;
/**
* 打印行走路径
*
* 经过的点用'*'表示,
* 未经过的点用'.'表示,
* 起始节点用'r'表示,
* 目的节点用'a'表示
* 士兵用'x'表示
*/
public void printPath() {
System.out.println("================ printPath ================");
Point father_point = null;
char[][] result = new char[7][8];
for (int i = 0; i < 7; i++) {
for (int j = 0; j < 8; j++) {
result[i][j] = '.';
}
}
int step = 0;
father_point = maze[goal.getX()][goal.getY()];
while (father_point != null) {
if(father_point.equals(start))
result[father_point.getX()][father_point.getY()] = 'r';
else if(father_point.equals(goal)) {
result[father_point.getX()][father_point.getY()] = 'a';
step++;
}
else if(father_point.getValue() == 'x') {
result[father_point.getX()][father_point.getY()] = 'x';
step += 2;
}
else {
result[father_point.getX()][father_point.getY()] = '*';
step++;
}
father_point = father_point.getFather();
}
// 打印行走步数
System.out.println("step is : " + step);
for (int i = 0; i < 7; i++) {
for (int j = 0; j < 8; j++) {
System.out.print(result[i][j] + " ");
}
System.out.println();
}
}
/**
* 构造函数
*
* @param maze 迷宫图
* @param start 起始节点
* @param goal 目的节点
*/
public AStar(Point[][] maze, Point start, Point goal) {
this.maze = maze;
this.start = start;
this.goal = goal;
openQueue = new LinkedList<Point>();
closedQueue = new LinkedList<Point>();
FList = new int[maze.length][maze[0].length];
GList = new int[maze.length][maze[0].length];
HList = new int[maze.length][maze[0].length];
for (int i = 0; i < maze.length; i++) {
for (int j = 0; j < maze[0].length; j++) {
FList[i][j] = Integer.MAX_VALUE;
GList[i][j] = Integer.MAX_VALUE;
HList[i][j] = Integer.MAX_VALUE;
}
}
init();
}
/*
* 初始化
*
* 将起始节点添加至开启列表,初始化:
* 1) 起始节点到当前节点(起始节点)的距离
* 2) 当前节点(起始节点)到目的节点的距离
* 3) 起始节点经过当前节点(起始节点)到目的节点的距离
*/
private void init() {
openQueue.offer(start);
int start_x = start.getX();
int start_y = start.getY();
int goal_x = goal.getX();
int goal_y = goal.getY();
// 起始节点到当前节点的距离
GList[start_x][start_y] = 0;
// 当前节点到目的节点的距离
HList[start_x][start_y] = getDistance(start_x, start_y, goal_x, goal_y);
// f(x) = g(x) + h(x)
FList[start_x][start_y] = GList[start_x][start_y]
+ HList[start_x][start_y];
}
/**
* 启动搜索迷宫过程主入口
*
* 从开启列表中搜索F值最小(即:起始节点 经过某一节点 到目的节点 距离最短),
* 将选取的节点作为当前节点,并更新当前节点的邻居节点信息(G、H、F值)以及
* 开启列表与关闭列表的成员。
*/
public void start() {
Point currentPoint;
while ((currentPoint = findShortestFPoint()) != null) {
if (currentPoint.getX() == goal.getX()
&& currentPoint.getY() == goal.getY())
return;
updateNeighborPoints(currentPoint);
}
}
public static void main(String[] args) {
// 原始迷宫图
char[][] mazeRaw = { { '#', '.', '#', '#', '#', '#', '#', '.' },
{ '#', '.', 'a', '#', '.', '.', 'r', '.' },
{ '#', '.', '.', '#', 'x', '.', '.', '.' },
{ '.', '.', '#', '.', '.', '#', '.', '#' },
{ '#', '.', '.', '.', '#', '#', '.', '.' },
{ '.', '#', '.', '.', '.', '.', '.', '.' },
{ '.', '.', '.', '.', '.', '.', '.', '.' } };
// 节点迷宫图
Point[][] maze = new Point[mazeRaw.length][mazeRaw[0].length];
for (int i = 0; i < maze.length; i++) {
for (int j = 0; j < maze[0].length; j++) {
maze[i][j] = new Point(i, j, mazeRaw[i][j]);
}
}
// 起始节点
Point start = maze[1][6];
// 目的节点
Point goal = maze[1][2];
AStar astar = new AStar(maze, start, goal);
// 启动搜索迷宫过程
astar.start();
// 打印行驶路径
astar.printPath();
}
/*
* 检查位置是否有效
*
* 如果当前位置存在、不是墙,且不在关闭列表中,则返回"true",表示为有效位置;
* 否则,返回"false"。
*
* 输入: 待检查位置的横坐标值
* 待检查位置的纵坐标值
*
* 输出: 是否有效
*/
private boolean checkPosValid(int x, int y) {
// 检查x,y是否越界, 并且当前节点不是墙
if ((x >= 0 && x < maze.length) && (y >= 0 && y < maze[0].length)
&& (maze[x][y].getValue() != '#')) {
// 检查当前节点是否已在关闭队列中,若存在,则返回 "false"
Iterator<Point> it = closedQueue.iterator();
Point point = null;
while (it.hasNext()) {
if ((point = it.next()) != null) {
if (point.getX() == x && point.getY() == y)
return false;
}
}
return true;
}
return false;
}
/*
* 获取当前位置到目的位置的距离
*
* 距离衡量规则: 横向移动一格或纵向移动一格的距离为1.
*
* 输入: 当前位置的横坐标值
* 当前位置的纵坐标值
* 目的位置的横坐标值
* 目的位置的纵坐标值
*
* 输出: 当前位置到目的位置的距离
*/
private int getDistance(int current_x, int current_y, int goal_x, int goal_y) {
return Math.abs(current_x - goal.getX())
+ Math.abs(current_y - goal.getY());
}
/*
* 找寻最短路径所经过的节点
*
* 从开启列表中找寻F值最小的节点,将其从开启列表中移除,并置入关闭列表。
*
* 输出:最短路径所经过的节点
*/
private Point findShortestFPoint() {
Point currentPoint = null;
Point shortestFPoint = null;
int shortestFValue = Integer.MAX_VALUE;
Iterator<Point> it = openQueue.iterator();
while (it.hasNext()) {
currentPoint = it.next();
if (FList[currentPoint.getX()][currentPoint.getY()] <= shortestFValue) {
shortestFPoint = currentPoint;
shortestFValue = FList[currentPoint.getX()][currentPoint.getY()];
}
}
if (shortestFValue != Integer.MAX_VALUE) {
openQueue.remove(shortestFPoint);
closedQueue.offer(shortestFPoint);
}
return shortestFPoint;
}
/*
* 更新邻居节点
*
* 依次判断上、下、左、右方向的邻居节点,如果邻居节点有效,则更新距离矢量表。
*
* 输入: 当前节点
*/
private void updateNeighborPoints(Point currentPoint) {
int current_x = currentPoint.getX();
int current_y = currentPoint.getY();
// 上
if (checkPosValid(current_x - 1, current_y)) {
updatePoint(maze[current_x][current_y],
maze[current_x - 1][current_y]);
}
// 下
if (checkPosValid(current_x + 1, current_y)) {
updatePoint(maze[current_x][current_y],
maze[current_x + 1][current_y]);
}
// 左
if (checkPosValid(current_x, current_y - 1)) {
updatePoint(maze[current_x][current_y],
maze[current_x][current_y - 1]);
}
// 右
if (checkPosValid(current_x, current_y + 1)) {
updatePoint(maze[current_x][current_y],
maze[current_x][current_y + 1]);
}
}
/*
* 更新节点
*
* 依次计算:1) 起始节点到当前节点的距离; 2) 当前节点到目的位置的距离; 3) 起始节点经过当前节点到目的位置的距离
* 如果当前节点在开启列表中不存在,则:置入开启列表,并且“设置”1)/2)/3)值;
* 否则,判断 从起始节点、经过上一节点到当前节点、至目的地的距离 < 上一次记录的从起始节点、到当前节点、至目的地的距离,
* 如果有更短路径,则更新1)/2)/3)值
*
* 输入: 上一跳节点(又:父节点)
* 当前节点
*/
private void updatePoint(Point lastPoint, Point currentPoint) {
int last_x = lastPoint.getX();
int last_y = lastPoint.getY();
int current_x = currentPoint.getX();
int current_y = currentPoint.getY();
// 起始节点到当前节点的距离
int temp_g = GList[last_x][last_y] + 1;
if (maze[current_x][current_y].getValue() == 'x') // 如果当前节点是看守
++temp_g;
// 当前节点到目的位置的距离
int temp_h = getDistance(current_x, current_y, goal.getX(), goal.getY());
// f(x) = g(x) + h(x)
int temp_f = temp_g + temp_h;
// 如果当前节点在开启列表中不存在,则:置入开启列表,并且“设置”
// 1) 起始节点到当前节点距离
// 2) 当前节点到目的节点的距离
// 3) 起始节点到目的节点距离
if (!openQueue.contains(currentPoint)) {
openQueue.offer(currentPoint);
currentPoint.setFather(lastPoint);
// 起始节点到当前节点的距离
GList[current_x][current_y] = temp_g;
// 当前节点到目的节点的距离
HList[current_x][current_y] = temp_h;
// f(x) = g(x) + h(x)
FList[current_x][current_y] = temp_f;
} else {
// 如果当前节点在开启列表中存在,并且,
// 从起始节点、经过上一节点到当前节点、至目的地的距离 < 上一次记录的从起始节点、到当前节点、至目的地的距离,
// 则:“更新”
// 1) 起始节点到当前节点距离
// 2) 当前节点到目的节点的距离
// 3) 起始节点到目的节点距离
if (temp_f < FList[current_x][current_y]) {
// 起始节点到当前节点的距离
GList[current_x][current_y] = temp_g;
// 当前节点到目的位置的距离
HList[current_x][current_y] = temp_h;
// f(x) = g(x) + h(x)
FList[current_x][current_y] = temp_f;
// 更新当前节点的父节点
currentPoint.setFather(lastPoint);
}
}
}
}
Point类:
package com.sabrina.astar;
public class Point {
// 节点横坐标
private int x;
// 节点纵坐标
private int y;
// 节点值
private char value;
// 父节点
private Point father;
/**
* 构造函数
*
* @param x 节点横坐标
* @param y 节点纵坐标
*/
public Point(int x, int y) {
this.x = x;
this.y = y;
}
/**
* 构造函数
*
* @param x 节点横坐标
* @param y 节点纵坐标
* @param value 节点值
*/
public Point(int x, int y, char value) {
this.x = x;
this.y = y;
this.value = value;
}
public Point getFather() {
return father;
}
public void setFather(Point father) {
this.father = father;
}
public char getValue() {
return value;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}
代码执行结果
================ printPath ================
step is : 13
. . . . . . . .
. . a . * * r .
. * * . x . . .
. * . * * . . .
. * * * . . . .
. . . . . . . .
. . . . . . . .
step is : 13
. . . . . . . .
. . a . * * r .
. * * . x . . .
. * . * * . . .
. * * * . . . .
. . . . . . . .
. . . . . . . .