关于A*算法的研究总结

关于A*算法的研究总结

重要公式: f ( n ) = g ( n ) + h ( n ) f(n)=g(n)+h(n) f(n)=g(n)+h(n)

其中:

  • f(n)是节点n的综合优先级。当我们选择下一个要遍历的节点时,我们总会选取综合优先级最高(值最小)的节点。
  • g(n) 是节点n距离起点的代价。
  • h(n)是节点n距离终点的预计代价,这也就是A*算法的启发函数。关于启发函数我们在下面详细讲解。

A*算法在运算过程中,每次从优先队列中选取f(n)值最小(优先级最高)的节点作为下一个待遍历的节点。

另外,A*算法使用两个集合来表示待遍历的节点,与已经遍历过的节点,这通常称之为open_setclose_set

完整的A*算法描述如下:

* 初始化open_set和close_set;
* 将起点加入open_set中,并设置优先级为0(优先级最高);
* 如果open_set不为空,则从open_set中选取优先级最高的节点n:
    * 如果节点n为终点,则:
        * 从终点开始逐步追踪parent节点,一直达到起点;
        * 返回找到的结果路径,算法结束;
    * 如果节点n不是终点,则:
        * 将节点n从open_set中删除,并加入close_set中;
        * 遍历节点n所有的邻近节点:
            * 如果邻近节点m在close_set中,则:
                * 跳过,选取下一个邻近节点
            * 如果邻近节点m也不在open_set中,则:
                * 设置节点m的parent为节点n
                * 计算节点m的优先级
                * 将节点m加入open_set中
启发函数

上面已经提到,启发函数会影响A*算法的行为。

  • 在极端情况下,当启发函数h(n)始终为0,则将由g(n)决定节点的优先级,此时算法就退化成了Dijkstra算法。
  • 如果h(n)始终小于等于节点n到终点的代价,则A*算法保证一定能够找到最短路径。但是当h(n)的值越小,算法将遍历越多的节点,也就导致算法越慢。
  • 如果h(n)完全等于节点n到终点的代价,则A*算法将找到最佳路径,并且速度很快。可惜的是,并非所有场景下都能做到这一点。因为在没有达到终点之前,我们很难确切算出距离终点还有多远。
  • 如果h(n)的值比节点n到终点的代价要大,则A*算法不能保证找到最短路径,不过此时会很快。
  • 在另外一个极端情况下,如果h(n)相较于g(n)大很多,则此时只有h(n)产生效果,这也就变成了最佳优先搜索。

由上面这些信息我们可以知道,通过调节启发函数我们可以控制算法的速度和精确度。因为在一些情况,我们可能未必需要最短路径,而是希望能够尽快找到一个路径即可。这也是A*算法比较灵活的地方。

对于网格形式的图,有以下这些启发函数可以使用:

  • 如果图形中只允许朝上下左右四个方向移动,则可以使用曼哈顿距离(Manhattan distance)。
    h ( n ) = D ∗ ( a b s ( c . x − e n d . x ) + a b s ( c . y − e n d . y ) ) h(n)=D*(abs(c.x-end.x)+abs(c.y-end.y)) h(n)=D(abs(c.xend.x)+abs(c.yend.y))

  • 如果图形中允许朝八个方向移动,则可以使用对角距离。

    有两种情况:

    ​ 1,假设行走的直线和对角线的代价都为D,则:
    h ( n ) = D ∗ M A X ( a b s ( c . x − e n d . x ) , a b s ( c . y − e n d . y ) ) h(n)=D*MAX(abs(c.x-end.x),abs(c.y-end.y)) h(n)=DMAX(abs(c.xend.x),abs(c.yend.y))
    ​ 2,假设行走的直线代价为D,对角线代价为 $ \sqrt{2} $D,则:

    h − d i a g o n a l ( n ) = M I N ( a b s ( c . x − e n d . x ) , a b s ( c . y − e n d . y ) ) h-diagonal(n)=MIN(abs(c.x-end.x),abs(c.y-end.y)) hdiagonal(n)=MIN(abs(c.xend.x),abs(c.yend.y))

    h − s t r a i n g h t ( n ) = a b s ( c . x − e n d . x ) + a b s ( c . y − e n d . y ) h-strainght(n)=abs(c.x-end.x)+abs(c.y-end.y) hstrainght(n)=abs(c.xend.x)+abs(c.yend.y)
    h ( n ) = 2 D ∗ h − d i a g o n a l ( n ) + D ∗ ( h − s t r a i n g h t ( n ) − 2 ∗ h − d i a g o n a l ( n ) ) h(n) =\sqrt{2}D*h-diagonal(n)+D*(h-strainght(n)-2*h-diagonal(n)) h(n)=2 Dhdiagonal(n)+D(hstrainght(n)2hdiagonal(n))

  • 如果图形中允许朝任何方向移动,则可以使用欧几里得距离(Euclidean distance)
    h ( n ) = D ∗ s q r t ( ( c . x − e n d . x ) 2 + ( c . y − e n d . y ) 2 ) h(n)=D*sqrt((c.x-end.x)^2+(c.y-end.y)^2) h(n)=Dsqrt((c.xend.x)2+(c.yend.y)2)


1,Q:从当前节点的周围节点中如何选取第一个检查节点?

  A:具有最小 F 值的那个。
/**
 * A*算法简易版实现
 * @author Xxx
 *
 */
public class Astar {
	
	public final static String BAR = "|"; 				// 障碍值
	public final static String PATH = "●"; 				// 路径
	public final static int DIRECT_VALUE = 10; 			// 横竖移动代价
	public final static int OBLIQUE_VALUE = 14; 		// 斜移动代价
	
	// 全局变量
	static Queue<Node> open_list = new PriorityQueue<Node>();
	static List<Node> close_list = new ArrayList<Node>();
	
	static long startTime = 0;
	static long endTime = 0;

	public static void main(String[] args) {
		String[][] maps = { 
				{ "□", "□", "□", "□", "□", "□", "", "□", "□", "□", "□", "□", "□", "□", "□" },
				{ "□", "□", "□", "□", "|", "|", "|", "|", "|", "|", "|", "□", "|", "|", "□" },
				{ "□", "|", "|", "|", "|", "□", "□", "□", "□", "|", "|", "|", "|", "|", "□" },
				{ "□", "□", "□", "|", "□", "□", "□", "□", "□", "|", "|", "□", "□", "□", "□" },
				{ "□", "□", "□", "|", "□", "□", "□", "□", "□", "|", "□", "□", "□", "□", "□" },
				{ "□", "□", "□", "|", "□", "□", "□", "□", "|", "□", "□", "□", "□", "□", "□" },
				{ "□", "□", "□", "|", "□", "□", "□", "□", "|", "□", "□", "□", "□", "□", "□" } 
				};
		MapInfo info=new MapInfo(maps,maps[0].length, maps.length,new Node(1, 6), new Node(4,3));

		startTime = System.currentTimeMillis();
		
		start(info);
		printMap(maps);

		endTime = System.currentTimeMillis() - startTime;
		System.out.println("花费的时间:" + endTime);
	}
	
	
	public static void start(MapInfo mapinfo) {
		
		open_list.clear();
		close_list.clear();
		
		// 起点加入open_list中来了
		open_list.add(mapinfo.start);
		
		// 判断起点和终点是否一致
		if((mapinfo.start.coord.x == mapinfo.end.coord.x)
				&&(mapinfo.start.coord.y == mapinfo.end.coord.y)) {
			System.out.println("起点就是终点,不用寻找路径了!");
			return;
		}
		
		/**
		 * 如果不一致的话
		 * 			1,将当前节点(起点)从open_list弹出,并加入到close_list中
		 * 			2,将当前节点(起点)周围的节点加入到open_list来【不可达节点不用加,已在open_list中的节点不用加,已在close_list中的节点不用加】,并且重新计算G和F值
		 */
//		Node current = open_list.poll();
//		close_list.add(current);
		
		while (!open_list.isEmpty())
		{
			// 一旦终点加入到了close_list中去,立马回溯起点,就可以找到路径了
			if (isCoordInClose(mapinfo.end.coord))
			{
				// 根据父节点回溯直至找到起点
				drawPath(mapinfo.maps, mapinfo.end);
				break;
			}
			// 弹出当前节点加入到close_list中
			Node current = open_list.poll();
			close_list.add(current);
			// 将当前节点的周围节点添加到open_list中
			// 上
			addNode2Open(mapinfo, current,current.coord.x,current.coord.y+1,DIRECT_VALUE);
			// 右
			addNode2Open(mapinfo, current,current.coord.x+1,current.coord.y,DIRECT_VALUE);
			// 下
			addNode2Open(mapinfo, current,current.coord.x,current.coord.y-1,DIRECT_VALUE);
			// 左
			addNode2Open(mapinfo, current,current.coord.x-1,current.coord.y,DIRECT_VALUE);
			
			// 左上
			addNode2Open(mapinfo, current,current.coord.x-1,current.coord.y+1,OBLIQUE_VALUE);
			// 右上
			addNode2Open(mapinfo, current,current.coord.x+1,current.coord.y+1,OBLIQUE_VALUE);
			// 右下
			addNode2Open(mapinfo, current,current.coord.x+1,current.coord.y-1,OBLIQUE_VALUE);
			// 左下
			addNode2Open(mapinfo, current,current.coord.x-1,current.coord.y-1,OBLIQUE_VALUE);
		}
		
	}
	
	/**
	 * 在二维数组中绘制路径
	 */
	private static void drawPath(String[][] maps, Node end)
	{
		if(end==null||maps==null) return;
		System.out.println("总代价:" + end.G);
		while (end != null)
		{
			Coord c = end.coord;
			maps[c.y][c.x] = PATH;
			end = end.parent;
		}
	}
	
	/**
	 * 将周围的邻节点加入到open_list中,并且重新计算G和F值
	 * 按照:上->右->下->左   左上->右上->右下->左下的顺序依次添加
	 * @param end
	 * @param current
	 * @param x
	 * @param y
	 * @param value
	 */
	public static void addNode2Open(MapInfo mapinfo, Node current, int x, int y, int value) {
		
		if(canAddNodeToOpen(mapinfo,x,y)) {
			Node end=mapinfo.end;
			// 当前邻节点的坐标
			Coord coord = new Coord(x, y);
			// 计算邻结点的G值(离起点的权值)
			int G = current.G + value;
			Node child = findNodeInOpen(coord);
			// child 不在open列表中的话
			if (child == null)
			{
				// 以下两种方式的路径不一样,但是花费的总代价是一样的
				int H = calcH(end.coord,coord)*DIRECT_VALUE; // 计算H值(离终点的预估权值)
				// 判断当前节点是否是终点
				if(isEndNode(end.coord,coord))
				{
					child = end;
					child.parent = current;
					child.G = G;
					child.H = H;
				}
				else
				{
					// 创建该节点并准备加入open列表中
					child = new Node(coord, current, G, H);
				}
				open_list.add(child);
			}else if (child.G > G)
			{
				child.G = G;
				child.parent = current;
				open_list.add(child);
			}
		}
	}
	
	/**
	 * 判断结点能否放入Open列表:
	 * 			1,不再地图中的节点不要放
	 * 			2,不可达的节点不要放
	 * 			3,在close_list中的节点不要放
	 */
	private static boolean canAddNodeToOpen(MapInfo mapInfo,int x, int y)
	{
		// 是否在地图中
		if (x < 0 || x >= mapInfo.width || y < 0 || y >= mapInfo.hight) return false;
		// 判断是否是不可通过的结点
		if (mapInfo.maps[y][x] == BAR) return false;
		// 判断结点是否存在close表
		if (isCoordInClose(x, y)) return false;

		return true;
	}
	
	/**
	 * 判断坐标是否在close表中
	 */
	private static boolean isCoordInClose(Coord coord)
	{
		return coord!=null&&isCoordInClose(coord.x, coord.y);
	}

	/**
	 * 判断坐标是否在close表中
	 */
	private static boolean isCoordInClose(int x, int y)
	{
		if (close_list.isEmpty()) return false;
		for (Node node : close_list)
		{
			if (node.coord.x == x && node.coord.y == y)
			{
				return true;
			}
		}
		return false;
	}
	
	/**
	 * 从Open列表中查找结点
	 */
	private static Node findNodeInOpen(Coord coord)
	{
		if (coord == null || open_list.isEmpty()) return null;
		for (Node node : open_list)
		{
			if (node.coord.equals(coord))
			{
				return node;
			}
		}
		return null;
	}
	
	/**
	 * 计算H的估值:“曼哈顿”法,坐标分别取差值相加
	 */
	private static int calcH(Coord end,Coord coord)
	{
		return Math.abs(end.x - coord.x)
				+ Math.abs(end.y - coord.y);
	}
	
	/**
	 * 判断结点是否是最终结点
	 */
	private static boolean isEndNode(Coord end,Coord coord)
	{
		return coord != null && end.equals(coord);
	}
	
	/**
	 * 打印地图
	 */
	public static void printMap(String[][] maps)
	{
		for (int i = 0; i < maps.length; i++)
		{
			for (int j = 0; j < maps[i].length; j++)
			{
				System.out.print(maps[i][j] + " ");
			}
			System.out.println();
		}
	}

}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值