很早就听说过A*算法,据说在寻路径时,是一种比较高效的算法。但是一直没有搞清楚原理。
这段时间刚好有个营救公主的例子:
题描述 :
公主被魔王抓走了 , 王子需要拯救出美丽的公主 。 他进入了魔王的城
堡 , 魔王的城堡是一座很大的迷宫 。 为了使问题简单化 , 我们假设这个迷宫是一
个 N*M 的二维方格 。 迷宫里有一些墙 , 王子不能通过 。 王子只能移动到相邻 ( 上
下左右四个方向 ) 的方格内 , 并且一秒只能移动一步 , 就是说 , 如果王子在 (x,y )
一步只能移动到 (x-1,y),(x+1,y),(x,y-1),(x,y+1) 其中的一个位置上。地图由
‘S’,‘P’,‘ . ’ , ‘ *’ 四种符号构成 , ‘ . ’ 表示王子可以通过 , ‘ *’ 表示
墙,王子不能通过;'S'表示王子的位置;‘P’表示公主的位置; n表示公主存活的剩余时间,王子必须在 n 秒
内到达公主的位置,才能救活公主。
解题思路:
1、可以通过广度优先的算法进行演进,不停的查找王子的所有下一点的位置,没查找一次时间减1,直到找到了公主或者时间为0时终止。
这个算法能够比较快速的解决上述的迷宫问题;
2、通过A*算法,查找出每次移动可能到达的所有点,并设置了一定的权值规则,每次选取权值最小的一个点找它的下一个点……(当然,这是搞懂原理之后的后话:) )
本着锻炼下自己的原则选择了A*算法解决上面的问题。
原理我就不班门弄斧了,详细请看牛人的博文:http://www.blueidea.com/tech/multimedia/2005/3121_3.asp,下面的回复中还有个牛人实现了个Flash版的A*算法。我个人比较笨,看了好几遍才明白意思。各位如果没接触过且想学的,不妨在纸上或者电脑上按照图示演算一遍,相信很快就能搞清楚原理:)
代码实现简要说明:
1、定义了一个迷宫类 Maze,迷宫中包含了王子Prince(包含核心算法)和迷宫的地图MazeMap,迷宫(游戏)启动时,会先初始化地图,然后王子开始寻路(具体算法看代码);
2、定义了一个位置类Position,描述了二维坐标信息,及加权的PositionWeight类,包含了位置信息、距离王子的距离(A*算法中的G)、距离公主的距离(A*算法中的H)、及二者的总开销(F=G+H);
相信看懂了A*算法的原理的朋友,很快就能写出一个迷宫的实现方案。
下面贴一下我的实现,注释还算比较详尽,欢迎批评指正:)
/** * 迷宫中的位置点 创建人:dobuy * */ public class Position { /** * 水平或者垂直移动一格的距离 */ private final static int STRAIGHT_DISTANCE = 10; /** * 对角线移动一格的距离 */ private final static int DIAGONAL_LINE_DISTANCE = 14; private int x; private int y; public Position(int x, int y) { super(); this.x = x; this.y = y; } /** * 获取从父节点直接偏移至子节点的距离 */ public int getOffsetOfDistance(Position position) { int x = Math.abs(getX() - position.getX()); int y = Math.abs(getY() - position.getY()); Position offset = new Position(x, y); if (offset.equals(new Position(0, 1)) || offset.equals(new Position(1, 0))) { return STRAIGHT_DISTANCE; } return DIAGONAL_LINE_DISTANCE; } /** * 获取到目标节点的平移距离 */ public int getDistanceOfTarget(Position position) { int verticalDistance = Math.abs(getY() - position.getY()); int horizontalDistance = Math.abs(getX() - position.getX()); return (verticalDistance + horizontalDistance) * STRAIGHT_DISTANCE; } public Position offset(Position offset) { return new Position(getX() + offset.getX(), getY() + offset.getY()); } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + x; result = prime * result + y; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Position other = (Position) obj; if (x != other.x) return false; if (y != other.y) return false; return true; } @Override public String toString() { return "Position [x=" + x + ", y=" + y + "]"; } }
/** * 位置信息的权值 */ public class PositionWeight { /** * 水平或者垂直移动一格的距离 */ private final static int STRAIGHT_DISTANCE = 10; /** * 当前的位置信息 */ private Position position; /** * 起始点(王子的起始位置),经由当前点的父节点后,到当前点的距离(仅包括垂直和水平直线上的) */ private int distanceOfPrince; /** * 当前点到目标点(公主位置)的距离 */ private int distanceOfPrincess; /** * 父节点的权值 */ private PositionWeight father; /** * 总开销:包括起始点到当前点和当前点到终点的开销之和 */ private int cost; public PositionWeight(Position position) { this.position = position; } public PositionWeight(Position position, PositionWeight father, PositionWeight target) { this(position); countDistanceToTarget(target); updateByFather(father); } /** * 获取父子节点间的距离:对角线为14,水平、垂直为10 */ public int getDistanceFromAttemptFather(PositionWeight father) { Position fatherPosition = father.getPosition(); return fatherPosition.getOffsetOfDistance(getPosition()); } /** * 更新父节点,并设置当前点的权值 */ public void updateByFather(PositionWeight father) { setFather(father); int distanceOfPrince = getDistanceFromAttemptFather(father); setDistanceOfPrince(distanceOfPrince + father.getDistanceOfPrince()); setCost(getDistanceOfPrince() + getDistanceOfPrincess()); } public Position getPosition() { return position; } public PositionWeight getFather() { return father; } public int getCost() { return cost; } public int getDistanceOfPrince() { return distanceOfPrince; } /** * 获取花费的总开销 */ public int getSpendTime() { return getCost() / STRAIGHT_DISTANCE; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((position == null) ? 0 : position.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; PositionWeight other = (PositionWeight) obj; if (position == null) { if (other.position != null) return false; } else if (!position.equals(other.position)) return false; return true; } @Override public String toString() { return "PositionWeight [position=" + position + ", distanceOfPrince=" + distanceOfPrince + ", distanceOfPrincess=" + distanceOfPrincess + ", father=" + father.getPosition() + ", cost=" + cost + "]"; } /** * 设置到目标节点的距离 */ private void countDistanceToTarget(PositionWeight target) { Position targetPosition = target.getPosition(); int distanceToTarget = getPosition() .getDistanceOfTarget(targetPosition); setDistanceOfPrincess(distanceToTarget); } private void setDistanceOfPrince(int distanceOfPrince) { this.distanceOfPrince = distanceOfPrince; } private int getDistanceOfPrincess() { return distanceOfPrincess; } private void setDistanceOfPrincess(int distanceOfPrincess) { this.distanceOfPrincess = distanceOfPrincess; } private void setFather(PositionWeight father) { this.father = father; } private void setCost(int cost) { this.cost = cost; } }
import java.util.ArrayList; import java.util.List; import javax.lang.model.element.UnknownElementException; /** * 迷宫地图 * * 类名称:Maze 类描述: 创建人:dobuy * */ public class MazeMap { /** * 迷宫中的原点(0,0) */ private Position originPosition; /** * 迷宫中的最大边界点 */ private Position edgePosition; /** * 王子的位置 */ private Position princePosition; /** * 公主的位置 */ private Position princessPosition; /** * 所有可达的位置集合(非墙壁) */ private List<Position> allReachablePositions; public MazeMap() { allReachablePositions = new ArrayList<Position>(); originPosition = new Position(0, 0); } public boolean isOverEdge(Position position) { if (getOriginPosition().getX() > position.getX() || getOriginPosition().getY() > position.getY() || getEdgePosition().getX() < position.getX() || getEdgePosition().getY() < position.getY()) { return true; } return false; } /** * 判断是否是墙 * */ public boolean isWall(Position currentPosition) { if (isOverEdge(currentPosition) || isPrincess(currentPosition) || getPrincePosition().equals(currentPosition)) { return false; } return !getAllReachablePositions().contains(currentPosition); } /** * 判断当前位置是否是公主位置 * */ public boolean isPrincess(Position currentPosition) { return getPrincessPosition().equals(currentPosition); } /** * 初始化迷宫地址(坐标转换成点对象),并解析出王子的位置和公主的位置 * * @param mazeMap 二维坐标表示的迷宫地图 * */ public void initMazeMap(char[][] mazeMap) { if (mazeMap == null || mazeMap.length <= 0) { throw new UnknownElementException(null, "null error"); } for (int column = 0; column < mazeMap[0].length; column++) { for (int row = 0; row < mazeMap.length; row++) { parseMazePosition(new Position(row, column), mazeMap[row][column]); } } edgePosition = new Position(mazeMap.length, mazeMap[0].length); } public Position getPrincePosition() { return princePosition; } public Position getPrincessPosition() { return princessPosition; } /** * 解析迷宫的位置信息 */ private void parseMazePosition(Position currentPosition, char thing) { switch (thing) { case '.': getAllReachablePositions().add(currentPosition); break; case '*': break; case 'S': setPrincePosition(currentPosition); break; case 'P': setPrincessPosition(currentPosition); break; default: throw new UnknownElementException(null, thing); } } private Position getOriginPosition() { return originPosition; } private Position getEdgePosition() { return edgePosition; } private void setPrincePosition(Position princePosition) { this.princePosition = princePosition; } private void setPrincessPosition(Position princessPosition) { this.princessPosition = princessPosition; } private List<Position> getAllReachablePositions() { return allReachablePositions; } }
import javax.lang.model.element.UnknownElementException; /** * 迷宫类:包含王子和迷宫地图两个对象 * */ public class Maze { /** * 王子 */ private Prince prince; /** * 迷宫地图 */ private MazeMap map; private boolean isInitOK = true; public Maze(int time, char[][] map) { this.map = new MazeMap(); prince = new Prince(time); initMap(map); } public void initMap(char[][] map) { try { getMap().initMazeMap(map); getPrince().setMap(getMap()); } catch (UnknownElementException e) { // TODO log isInitOK = false; } } /** * 游戏开始:返回结果:-1表示营救失败;0表示营救成功 * */ public int start() { if (!isInitOK) { return -1; } return getPrince().startToSearch(); } private MazeMap getMap() { return map; } private Prince getPrince() { return prince; } }
import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * 王子 * * 类名称:Prince 类描述: 创建人:dobuy * */ public class Prince { /** * 营救公主失败 */ private final static int FAIL = -1; /** * 营救公主成功 */ private final static int SUCCESS = 0; /** * 剩余的时间 */ private int time; /** * 迷宫地图 */ private MazeMap map; /** * 正待尝试的位置集合(开启列表) */ private List<PositionWeight> attemptPositions; /** * 已经经过的位置集合(关闭列表) */ private List<PositionWeight> passedPositions; /** * 公主位置 */ private PositionWeight princessPosition; /** * 王子位置 */ private PositionWeight princePosition; /** * 王子移动一步的所有偏移量 */ private List<Position> offsets = Arrays.asList(new Position[] { new Position(1, 0), new Position(0, 1), new Position(-1, 0), new Position(0, -1) }); public Prince(int time) { this.time = time; this.attemptPositions = new ArrayList<PositionWeight>(); this.passedPositions = new ArrayList<PositionWeight>(); } /** * 开始寻找公主 */ public int startToSearch() { reset(); if (getPrincePosition().getPosition() == null || getPrincessPosition().getPosition() == null || time < 0) { return FAIL; } // 1、添加王子自己的起始位置 getAttemptPositions().add(getPrincePosition()); // 2、通过移动维护待尝试列表和已经尝试的列表 attemptMove(); // 3、已经营救成功或者时间耗尽或者无法营救时,统计结果返回 return getSpendTime(); } /** * 设置迷宫地图 */ public void setMap(MazeMap map) { this.map = map; } /** * 重置 * */ private void reset() { // 清空待尝试的列表 getAttemptPositions().clear(); // 清空已经尝试的列表 getPassedPositions().clear(); // 初始化王子的位置 Position princePosition = getMap().getPrincePosition(); setPrincePosition(new PositionWeight(princePosition)); // 初始化公主的位置 Position princessPosition = getMap().getPrincessPosition(); PositionWeight princessPositionWeight = new PositionWeight( princessPosition); setPrincessPosition(princessPositionWeight); } /** * 可预知式移动 * */ private void attemptMove() { // 1、在如下2种情况下均结束:1)只要在待尝试列表中发现了公主,表明已经找到; 2)迷宫中所有可达的点都遍历完成,仍然无法找到 if (getAttemptPositions().contains(getPrincessPosition()) || getAttemptPositions().isEmpty()) { return; } // 2、获取最新加入的开销最小的节点 PositionWeight minPositionWeight = getMinPositionWeight(); // 3、从待尝试列表中移除开销最小节点 getAttemptPositions().remove(minPositionWeight); // 4、把找到的开销最小节点加至已经尝试的列表 getPassedPositions().add(minPositionWeight); // 5、对当前的开销最小节点进行尝试,找出其所有子节点 List<PositionWeight> subPositionWeights = getReachableSubPositions(minPositionWeight); // 6、把所有子节点按照一定条件添加至待尝试列表 for (PositionWeight subPositionWeight : subPositionWeights) { addPositionWeight(minPositionWeight, subPositionWeight); } // 7、重复以上操作 attemptMove(); } /** * 王子从当前移动一步,可达的位置(忽略墙) * */ private List<PositionWeight> getReachableSubPositions(PositionWeight father) { List<PositionWeight> subPositionWeights = new ArrayList<PositionWeight>(); Position fatherPosition = father.getPosition(); PositionWeight subPositionWeight = null; Position subPosition = null; for (Position offset : offsets) { subPosition = fatherPosition.offset(offset); subPositionWeight = new PositionWeight(subPosition, father, getPrincessPosition()); // 子节点越界或者是墙壁或者已经在尝试过的列表中时,不做任何处理 if (getMap().isOverEdge(subPosition) || getMap().isWall(subPosition) || isInPassedTable(subPositionWeight)) { continue; } subPositionWeights.add(subPositionWeight); } return subPositionWeights; } /** * 添加一个点 * */ private void addPositionWeight(PositionWeight father, PositionWeight positionWeight) { // 在待尝试列表中已经包含了当前点,则按照一定条件更新其父节点及其权值,否则直接添加 if (getAttemptPositions().contains(positionWeight)) { updateCostByFather(father, positionWeight); } else { getAttemptPositions().add(positionWeight); } } /** * 计算花费的时间 */ private int getSpendTime() { if (getAttemptPositions().contains(getPrincessPosition())) { int princessIndex = getAttemptPositions().indexOf( getPrincessPosition()); PositionWeight princess = getAttemptPositions().get(princessIndex); return princess.getSpendTime() <= time ? SUCCESS : FAIL; } return FAIL; } /** * 从待尝试列表中查找总开销值最小的点(如果有几个相同开销的最小点,取靠近队尾的) * */ private PositionWeight getMinPositionWeight() { PositionWeight minPositionWeight = getAttemptPositions().get(0); for (PositionWeight positionWeight : getAttemptPositions()) { if (minPositionWeight.getCost() >= positionWeight.getCost()) { minPositionWeight = positionWeight; } } return minPositionWeight; } /** * 如果从父节点移动至子节点的G值小于子节点之前的G值(前提是子节点已经在开启列表中),则更新子节点的父节点及G值 */ private void updateCostByFather(PositionWeight father, PositionWeight subPosition) { int distanceOfAttemptFather = subPosition .getDistanceFromAttemptFather(father); int distanceOfPrince = father.getDistanceOfPrince() + distanceOfAttemptFather; if (distanceOfPrince < subPosition.getDistanceOfPrince()) { subPosition.updateByFather(father); } } private MazeMap getMap() { return map; } private boolean isInPassedTable(PositionWeight positionWeight) { return getPassedPositions().contains(positionWeight); } private List<PositionWeight> getAttemptPositions() { return attemptPositions; } private List<PositionWeight> getPassedPositions() { return passedPositions; } private PositionWeight getPrincessPosition() { return princessPosition; } private void setPrincessPosition(PositionWeight princessPosition) { this.princessPosition = princessPosition; } private PositionWeight getPrincePosition() { return princePosition; } private void setPrincePosition(PositionWeight princePosition) { this.princePosition = princePosition; } }
单元测试类:
import static org.junit.Assert.assertEquals; import org.junit.Test; /** * * 类名称:MazeTest 类描述: 创建人:dobuy * */ public class MazeTest { private Maze maze; private char[][] map; /** * 营救公主失败 */ private final static int FAIL = -1; /** * 营救公主成功 */ private final static int SUCCESS = 0; /** * testStart01 正常可达情况 */ @Test public void testStart01() { map = new char[][] { { '.', '.', '.', '.' }, { '.', '.', '.', '.' }, { '.', '.', '.', '.' }, { 'S', '*', '*', 'P' } }; maze = new Maze(5, map); assertEquals(maze.start(), SUCCESS); } /** * testStart02 正常不可达情况 */ @Test public void testStart02() { map = new char[][] { { '.', '.', '.', '.' }, { '.', '.', '.', '.' }, { '.', '.', '.', '.' }, { 'S', '*', '*', 'P' } }; maze = new Maze(2, map); assertEquals(maze.start(), FAIL); } /** * testStart03 参数异常 */ @Test public void testStart03() { map = null; maze = new Maze(2, map); assertEquals(maze.start(), FAIL); map = new char[][] {}; maze = new Maze(2, map); assertEquals(maze.start(), FAIL); map = new char[][] { { '.', '.', '.', '.' }, { '.', '.', '.', '.' }, { '.', '.', '.', '.' }, { '.', '.', '.', '.' } }; maze = new Maze(2, map); assertEquals(maze.start(), FAIL); map = new char[][] { { '.', '.', '.', '.' }, { '.', '.', '.', '.' }, { 'S', '.', '.', 'P' }, { '.', '.', '.', '.' } }; maze = new Maze(-1, map); assertEquals(maze.start(), FAIL); } /** * testStart04 临界值 */ @Test public void testStart04() { map = new char[][] { { '*', '*', '*', '*' }, { '*', '*', '*', '*' }, { '*', '*', '*', '.' }, { 'S', '*', '*', 'P' } }; maze = new Maze(2, map); assertEquals(maze.start(), FAIL); map = new char[][] { { '.', '.', '.', '.' }, { '.', '.', '.', '.' }, { 'S', 'P', '.', '*' }, { '.', '.', '.', '.' } }; maze = new Maze(1, map); assertEquals(maze.start(), SUCCESS); } }