算法07-A-star
一、介绍
A-Star(A*)算法是一种静态路网中求解最短路最有效的方法。它使用估价函数表示走下一个点时的路程,然后从能走的点中选取路程最小的那个点开始走,直到走到终点。估价函数公式表示为:f(n)=g(n)+h(n),其中f(n)是节点n从初始点到目标点的估价函数,g(n)是在状态空间中从初始节点到n节点的实际代价,h(n)是从n到目标节点最佳路径的估计代价。
A-Star算法分为简化版和完整版。
A-Star算法的应用:游戏找路。
二、A-Star简化版
1、基本思想
A-Star算法简化版只需把走过的点标记为1,当到达终点时,所有为1的点,即为最短路径。适用于没有凹形障碍物的情形,效率比完整版高。
操作步骤:
- 从起点开始,通过估价函数f(n),判断周围8个点中,使得路径最短的那个点A,将这个点标记为1(代表走过);
- 把A当作新的起点,重复步骤1,直到到达终点。
2、关键代码
/**
* @param map 用一个二维数组表示地图。其中0表示走得通,1表示走过,2表示终点,3表示不能走。
* @param star 起点
* @param end 终点
*/
public static void AStarSort(int[][] map, Point star, Point end) {
if (star == null && end == null) {
return;
}
if (star.x >= map.length - 1 || star.y >= map[0].length - 1) {
return;
}
//到达终点
if (star.x == end.x && star.y == end.y) {
return;
}
// 标记当前位置已走过
map[star.x][star.y] = 1;
//查找下一个能走的点
Point next = lookup(map, star, end);
//以next为起点,重新找寻
AStarSort(map, next, end);
}
/**
* 查找下一个要走的点
*
* @param map 地图
* @param point 当前点
* @param end 终点
* @return 下一个要走的点
*/
public static Point lookup(int[][] map, Point point, Point end) {
//保存所有可走的点
List<Point> result = new ArrayList<>();
// 表示1维数组下标
int x = point.x;
// 表示2维数组下标
int y = point.y;
// 如果左边可以走,就添加到result
if (map[x - 1][y] != 1 && map[x - 1][y] != 3) {
result.add(new Point(x - 1, y));
}
// 如果上边可以走,就添加到result
if (map[x][y - 1] != 1 && map[x][y - 1] != 3) {
result.add(new Point(x, y - 1));
}
// 如果右边可以走,就添加到result
if (map[x + 1][y] != 1 && map[x + 1][y] != 3) {
result.add(new Point(x + 1, y));
}
// 如果下边可以走,就添加到result
if (map[x][y + 1] != 1 && map[x][y + 1] != 3) {
result.add(new Point(x, y + 1));
}
// 如果左上可以走,就添加到result
if (map[x - 1][y - 1] != 1 && map[x - 1][y - 1] != 3) {
result.add(new Point(x - 1, y - 1));
}
// 如果右下可以走,就添加到result
if (map[x + 1][y - 1] != 1 && map[x + 1][y - 1] != 3) {
result.add(new Point(x + 1, y - 1));
}
// 如果左下可以走,就添加到result
if (map[x - 1][y + 1] != 1 && map[x - 1][y + 1] != 3) {
result.add(new Point(x - 1, y + 1));
}
// 如果右下可以走,就添加到result
if (map[x + 1][y + 1] != 1 && map[x + 1][y + 1] != 3) {
result.add(new Point(x + 1, y + 1));
}
//如果没有可走的点,就退出寻路
if (result.size() == 0) {
return null;
}
int index = 0;
int dstance = Integer.MAX_VALUE;
for (int i = 0; i < result.size(); i++) {
//使用估价函数,估算两点的距离
int l = getDistance(point, result.get(i), end);
//找到距离最小的那个点
if (dstance > l) {
index = i;
dstance = l;
}
}
return result.get(index);
}
/**
* 估价函数:起点与节点的距离+节点与终点的距离
*
* @param start 起点
* @param node 节点
* @param end 终点
* @return 估价的距离
*/
public static int getDistance(Point start, Point node, Point end) {
return getDistance(start, node) + getDistance(node, end);
}
/**
* 计算两点距离
*
* @param start 起点
* @param end 终点
* @return 两点距离
*/
public static int getDistance(Point start, Point end) {
if (start == null || end == null) {
return Integer.MAX_VALUE;
}
int a = end.x - start.x;
int b = end.y - start.y;
int c = (int) Math.sqrt(a * a + b * b);
return c;
}
三、A-Star完整版
1、基本思想
A-Star算法完整版每次从所有能走的点中选取最小的点A开始走,然后以A为节点动态更新所有能走的点的估价距离,并且保存A的上一个节点。当走到重点时,就可以通过保存的上一个节点,回退实际的最短路线。
A-Star算法完整版适用于有凹形障碍物的情形。
操作步骤:
- 声明openList保存所有能走的点,声明closeList保存所有走过的点,把起点start添加到openList中
- 从openList中最小的点A开始走,把A添加到closeList中,然后重新查找所有可走的点。如果找到的点p之前不在openList中,把它直接添加到openList中;如果p之前就在openList中,判断经过节点A时p的估价L1与走原来方向时p的估价距离L2的大小,如果L1<L2,就改走A,把A设为p的parent节点,把L1设为p的估价距离,然后对openList重新排序;
- 重复步骤2,直到到达终点。
- 最后,根据终点end的parent,可以回推实际的最短路线。
2、代码实现
/**
* @param mapInfo 地图
* @return 实际的最短路线
*/
public static Stack<Node> AStarSort(MapInfo mapInfo) {
if (mapInfo == null) {
return null;
}
// 使用自动排序队列表示能走的点
Queue<Node> openList = new PriorityQueue<>();
// 已经走过的点
List<Node> closeList = new ArrayList<>();
openList.add(mapInfo.start);
//如果有路可以走
while (!openList.isEmpty()) {
// 到达终点
if (isColsed(closeList, mapInfo.end)) {
//展示路线
Stack<Node> result = drawPath(mapInfo);
return result;
}
// 获取最小的节点
Node now = openList.poll();
//标记now已走过
closeList.add(now);
//寻找下一个可走的点
addNext(mapInfo, openList, closeList, now);
}
return null;
}
/**
* 寻找下一个可走的点
*
* @param mapInfo 地图
* @param openList 能走的点
* @param closeList 走过的点
* @param now 当前点
*/
private static void addNext(MapInfo mapInfo, Queue<Node> openList, List<Node> closeList, Node now) {
//一维数组的下标
int x = now.p.x;
//二维数组的下标
int y = now.p.y;
addNext(mapInfo, openList, closeList, now, new Point(x - 1, y), DIRECT_VALUE);// 左
addNext(mapInfo, openList, closeList, now, new Point(x, y - 1), DIRECT_VALUE);// 上
addNext(mapInfo, openList, closeList, now, new Point(x + 1, y), DIRECT_VALUE);// 右
addNext(mapInfo, openList, closeList, now, new Point(x, y + 1), DIRECT_VALUE);// 下
addNext(mapInfo, openList, closeList, now, new Point(x - 1, y - 1), OBLIQUE_VALUE);// 左上
addNext(mapInfo, openList, closeList, now, new Point(x + 1, y - 1), OBLIQUE_VALUE);// 右上
addNext(mapInfo, openList, closeList, now, new Point(x - 1, y + 1), OBLIQUE_VALUE);// 左下
addNext(mapInfo, openList, closeList, now, new Point(x + 1, y + 1), OBLIQUE_VALUE);// 右下
}
/**
* 寻找下一个可走的点
*
* @param mapInfo 地图
* @param openList 能走的点
* @param closeList 走过的点
* @param now 当前点
* @param p 下一个准备走的点
* @param value 从now到p的距离
*/
private static void addNext(MapInfo mapInfo, Queue<Node> openList, List<Node> closeList, Node now, Point p, int value) {
//先判断当前点能否添加到openList中:如果已经走过或者是障碍物,就不添加到openList中
if (canAddOpen(mapInfo, closeList, p)) {
Node end = mapInfo.end;
// 从起点到p的距离
int g = now.g + value;
//从openList找p
Node child = findOpen(openList, p);
//如果是第一次遇到这个点
if (child == null) {
//p到终点的距离
int h = calcH(end.p, p) * DIRECT_VALUE;
//如果到达终点,就更新end的值,然后添加到openList中
if (p.equals(end.p)) {
child = end;
//设置parent是为从end节点推路线图
child.parent = now;
child.g = g;
child.h = h;
//如果不是终点,就新建一个对象
} else {
child = new Node(p, now, g, h);
}
//添加到openList中
openList.add(child);
//如果p已经在openList中,走当前路线的距离小于原来路线的距离,就用当前的g值替换原来的g值
} else if (child.g > g) {
child.g = g;
//从now节点走,比原来的路线近,所以将它的parent设为now
child.parent = now;
// 重新排序,以便选择距离最小的点
openList.add(child);
}
}
}
最后
代码地址:https://gitee.com/yanhuo2008/Common/tree/master/Tool/src/main/java/gsw/tool/arithmetic/astar
数据结构与算法专题:https://www.jianshu.com/nb/25128590
喜欢请点赞,谢谢!