A*算法(A-star algorithm)是一种常用的启发式搜索算法,用于求解图中两个节点之间的最短路径。A算法综合了Dijkstra算法的广度优先搜索和贪婪最优搜索策略,通过使用估计函数(heuristic function)来指导搜索方向,从而提高搜索效率。
以下是A*算法的详细步骤:
- 初始化:将起始节点添加到一个开放列表(open list)中,并将其估计函数值(f值)设置为0。
- 循环直到开放列表为空或找到目标节点:
a. 从开放列表中选择具有最小估计函数值(f值)的节点,将其称为当前节点,并将其移出开放列表。
b. 如果当前节点是目标节点,则搜索完成,构建最短路径。
c. 否则,对于当前节点的每个邻居节点:- 如果邻居节点不可通过或已在关闭列表中,则忽略它。
- 如果邻居节点不在开放列表中,则将其添加到开放列表,并计算它的估计函数值(f值)和代价函数值(g值)。
- 如果邻居节点已经在开放列表中,并且通过当前节点到达它的代价函数值(g值)更小,则更新它的父节点和代价函数值(g值)。
- 如果开放列表为空且未找到目标节点,则表示无法到达目标节点,搜索失败。
A*算法使用了一个估计函数来指导搜索,在每次选择下一个节点时,它考虑了两个值:g值和h值。
- g值表示从起始节点到当前节点的实际代价。
- h值表示从当前节点到目标节点的估计代价。
A*算法的估计函数f值定义为:f(n) = g(n) + h(n),其中n是当前节点。f值越小,越有可能是优先考虑的节点。
A*算法的性能和效果取决于所选择的估计函数。一个好的估计函数应该能够提供较为准确的估计值,以便更好地引导搜索,但也需要具备一定的效率,以免过多地增加计算复杂度。
A算法的优点是在适当的估计函数下,能够找到最短路径,并且相对于其他完全搜索算法,它的搜索效率更高。然而,A算法并不能保证一定找到最短路径,且在某些情况下可能会陷入局部最优解。
节点代码
class Node {
int x; // 节点的x坐标
int y; // 节点的y坐标
int g; // 从起始节点到当前节点的实际代价
int h; // 从当前节点到目标节点的估计代价
int f; // 估计函数值
Node parent; // 父节点
public Node(int x, int y) {
this.x = x;
this.y = y;
}
// 必须重写equals方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Node node = (Node) o;
return x == node.x && y == node.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
}
算法实现
public class AStar {
public static void main(String[] args) {
int[][] grid = {
{0, 0, 0, 0, 0},
{1, 1, 0, 1, 0},
{0, 0, 0, 0, 0},
{0, 1, 1, 1, 1},
{0, 0, 0, 0, 0}
};
Node start = new Node(0, 0);
Node end = new Node(4, 4);
List<Node> path = findShortestPath(grid, start, end);
if (path != null) {
for (Node node : path) {
System.out.print("(" + node.x + ", " + node.y + ")");
System.out.print("-> ");
}
System.out.print("end");
} else {
System.out.println("No path found");
}
}
public static List<Node> findShortestPath(int[][] grid, Node start, Node end) {
int rows = grid.length;
int cols = grid[0].length;
//创建开放 关闭列表
List<Node> openList = new ArrayList<>();
List<Node> closeList = new ArrayList<>();
//初始化开始节点
start.g = 0;// 从起始节点到当前节点的实际代价
start.h = calculateHeuristic(start, end);// 从当前节点到目标节点的估计代价
start.f = start.g + start.h;
//添加进开放列表
openList.add(start);
//开放列表不为null就一直循环
while (!openList.isEmpty()) {
//从开放列表中选f最小的节点
Node current = getMinimumFValueNode(openList);
//将当前节点从开放列表中删除
openList.remove(current);
//加入关闭列表
closeList.add(current);
if (current.equals(end)) {
//找到最短路径,回溯到起始节点
return buildPath(current);
}
//当前节点的邻居节点
List<Node> neighbors = getNeighbors(current, grid, rows, cols);
for (Node neighbor : neighbors) {
if(closeList.contains(neighbor)){
//已访问
continue;
}
//当前节点移动到邻居节点 实际代价为1
int tentativeG = current.g+1;
// 如果开放列表中没有该几点,或者 存在该几点但是之走前路径的代价较大
if(!openList.contains(neighbor)||tentativeG<neighbor.g){
neighbor.parent = current;
neighbor.g = tentativeG;
neighbor.h = calculateHeuristic(neighbor,end);
neighbor.f=neighbor.g+neighbor.h;
//已经在里面 就不要添加了
if(!openList.contains(neighbor)){
openList.add(neighbor);
}
}
}
}
return null; //无法找到最短路径
}
// 计算预估代价
private static int calculateHeuristic(Node current, Node end) {
// 使用曼哈顿距离作为估计函数
return Math.abs(current.x - end.x) + Math.abs(current.y - end.y);
}
// 得到列表中 f 最小的节点
private static Node getMinimumFValueNode(List<Node> nodes) {
int minF = Integer.MAX_VALUE;
Node minNode = null;
for (Node node : nodes) {
if (node.f < minF) {
minF = node.f;
minNode = node;
}
}
return minNode;
}
// 通过节点的parent 构建出路径
private static List<Node> buildPath(Node node) {
List<Node> path = new ArrayList<>();
while (node != null) {
path.add(0, node);
node = node.parent;
}
return path;
}
// 得到邻居节点列表
private static List<Node> getNeighbors(Node node, int[][] grid, int rows, int cols) {
List<Node> neighbors = new ArrayList<>();
int[][] directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; // 上下左右四个方向
for (int[] direction : directions) {
int newX = node.x + direction[0];
int newY = node.y + direction[1];
if (newX >= 0 && newX < rows && newY >= 0 && newY < cols && grid[newX][newY] == 0) {
// 邻居节点在有效范围内且不是障碍物
Node neighbor = new Node(newX, newY);
neighbors.add(neighbor);
}
}
return neighbors;
}
}
使用优先队列
public class AStarPriority {
public static void main(String[] args) {
int[][] grid = {
{0, 0, 0, 0, 0},
{1, 1, 0, 1, 0},
{0, 0, 0, 0, 0},
{0, 1, 1, 1, 1},
{0, 0, 0, 0, 0}
};
Node start = new Node(0, 0);
Node end = new Node(4, 4);
List<Node> path = findShortestPath(grid, start, end);
if (path != null) {
for (Node node : path) {
System.out.print("(" + node.x + ", " + node.y + ")");
System.out.print("-> ");
}
System.out.print("end");
} else {
System.out.println("No path found");
}
}
public static List<Node> findShortestPath(int[][] grid, Node start, Node end) {
int rows = grid.length;
int cols = grid[0].length;
//优先队列
PriorityQueue<Node> queue = new PriorityQueue<>(rows + cols, Comparator.comparingInt(o -> o.f));
//已访问的节点
Set<Node> visited = new HashSet<>(rows + cols);
//处理开始节点
start.g = 0; //开始几点到当前节点的代价
start.h = guess(start, end); //猜测当前节点到终点的代价
//计算的代价
start.f = start.g + start.h;
//将当前节点添加到优先队列中
queue.offer(start);
while (!queue.isEmpty()) {
//代价最小(f最小)的节点出队列
Node current = queue.poll();
//将当前节点加入 已访问
visited.add(current);
//如果当前节点等于end
if (current.equals(end)) {
//找到了
return buildPath(current);
}
//当前节点的邻居
List<Node> neighbors = currentNeighbors(current, grid, rows, cols);
//遍历邻居
for (Node neighbor : neighbors) {
//访问过 就跳过
if (visited.contains(neighbor))
continue;
//从当前节点到邻居 花费代价1
int tentativeG = current.g + 1;
//如果当前节点没有在队列中 或者 当前路径代价更小
if (!queue.contains(neighbor) || tentativeG < neighbor.g) {
//更新邻居节点
neighbor.parent = current;
neighbor.g = tentativeG;
neighbor.h = guess(neighbor, end);
neighbor.f = neighbor.g + neighbor.h;
if(!queue.contains(neighbor)){
queue.offer(neighbor);
}
}
}
}
return null;
}
private static List<Node> currentNeighbors(Node current, int[][] grid, int rows, int cols) {
//上下左右4个方向
int[][] dir = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
List<Node> neighbors = new ArrayList<>();
for (int[] d : dir) {
int nx = current.x + d[0];//下一个几点的x
int ny = current.y + d[1];//下一个几点的y
//是否合法
if (nx < 0 || nx >= rows || ny < 0 || ny >= cols || grid[nx][ny] == 1)
continue;
neighbors.add(new Node(nx, ny));
}
return neighbors;
}
private static List<Node> buildPath(Node current) {
List<Node> path = new ArrayList<>();
Node temp = current;
while (temp != null) {
path.add(0,temp);
temp = temp.parent;
}
return path;
}
private static int guess(Node current, Node end) {
//当前节点到终点的曼哈顿距离: 曼哈顿距离比直线距离好算,不用开方
return Math.abs(current.x - end.x) + Math.abs(current.y - end.y);
}
}