现在的位置: 首页 > 综合 > 正文

营救公主(Java实现A*算法解决迷宫问题)

2013年11月26日 ⁄ 综合 ⁄ 共 13959字 ⁄ 字号 评论关闭

很早就听说过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);
	}
}

抱歉!评论已关闭.