A* (A Star) 算法 java实现

A*算法(A-star algorithm)是一种常用的启发式搜索算法,用于求解图中两个节点之间的最短路径。A算法综合了Dijkstra算法的广度优先搜索和贪婪最优搜索策略,通过使用估计函数(heuristic function)来指导搜索方向,从而提高搜索效率。

以下是A*算法的详细步骤:

  1. 初始化:将起始节点添加到一个开放列表(open list)中,并将其估计函数值(f值)设置为0。
  2. 循环直到开放列表为空或找到目标节点:
    a. 从开放列表中选择具有最小估计函数值(f值)的节点,将其称为当前节点,并将其移出开放列表。
    b. 如果当前节点是目标节点,则搜索完成,构建最短路径。
    c. 否则,对于当前节点的每个邻居节点:
    • 如果邻居节点不可通过或已在关闭列表中,则忽略它。
    • 如果邻居节点不在开放列表中,则将其添加到开放列表,并计算它的估计函数值(f值)和代价函数值(g值)。
    • 如果邻居节点已经在开放列表中,并且通过当前节点到达它的代价函数值(g值)更小,则更新它的父节点和代价函数值(g值)。
  3. 如果开放列表为空且未找到目标节点,则表示无法到达目标节点,搜索失败。

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);
    }
}

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值