很早就听说过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 allReachablePositions;
public MazeMap()
{
allReachablePositions = new ArrayList();
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 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 attemptPositions;
/**
* 已经经过的位置集合(关闭列表)
*/
private List passedPositions;
/**
* 公主位置
*/
private PositionWeight princessPosition;
/**
* 王子位置
*/
private PositionWeight princePosition;
/**
* 王子移动一步的所有偏移量
*/
private List 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();
this.passedPositions = new ArrayList();
}
/**
* 开始寻找公主
*/
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 subPositionWeights = getReachableSubPositions(minPositionWeight);
// 6、把所有子节点按照一定条件添加至待尝试列表
for (PositionWeight subPositionWeight : subPositionWeights)
{
addPositionWeight(minPositionWeight, subPositionWeight);
}
// 7、重复以上操作
attemptMove();
}
/**
* 王子从当前移动一步,可达的位置(忽略墙)
*
*/
private List getReachableSubPositions(PositionWeight father)
{
List subPositionWeights = new ArrayList();
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 getAttemptPositions()
{
return attemptPositions;
}
private List 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);
}
}