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 . . .
. * . * * . . .
. * * * . . . .
. . . . . . . .
. . . . . . . .