读书笔记——漫画算法(9) A*寻路算法

A* 寻路算法的讲解,这里有介绍比较清晰的文章:
A* Pathfinding for Beginners

在这里插入图片描述
首先引入两个集合,一个公式:

  • OpenList:可到达的格子
  • CloseList:已经走过的格子
  • F = G + H
  • 其中,每一个格子都有三个属性值:F,G,H
    • G:从起点走到当前格子的成本, 已经花费了多少步
    • H:不考虑障碍的情况下,从当前格子走到目标格子的距离,离目标还有多远
    • F:G和H的综合评估,也就是从起点到达当前格子,再从当前格子到达目标格子的总步数

算法执行的大致步骤如下:

  • 1. 从OpenList中找出F值最小的Grid,作为当前我们需要走的路径格子
  • 2. 于是我们从openList中移除此Grid,并加入到CloseList中
  • 3. 寻找当前节点的四周邻居节点,如果是可以走的路径(不越界,非墙,不在OpenList,CloseList中),则设置当前节点为其父节点并将其加入到OpenList中
  • 4. 如果OpenList中已经存在包含了终点格子,则直接返回
  • 5. OpenList元素个数大于0,则继续1步骤

下面是A*算法的完整实现,并带有测试用例:

/**
 *     A*算法(启发式搜索):
 * 首先引入两个集合,一个公式:
 * OpenList:可到达的格子
 * CloseList:已到达的格子
 * F = G + H
 *
 * 每一个格子都有三个属性值:F,G,H
 * G:从起点走到当前格子的成本, **已经花费了多少步**
 * H:不考虑障碍的情况下,从当前格子走到目标格子的距离,**离目标还有多远**
 * F:G和H的综合评估,也就是从起点到达当前格子,**再从当前格子到达目标格子的总步数**
 *
 */
public class AStarPathSearching {

    public final static int[][] MAZE = {
            {0, 0, 0, 0, 0, 0, 0},
            {0, 0, 0, 1, 0, 0, 0},
            {0, 0, 0, 1, 0, 0, 0},
            {0, 0, 0, 1, 0, 0, 0},
            {0, 0, 0, 0, 0, 0, 0}
    };

    private static class Grid {
        int x, y;
        int f, g, h;
        Grid parent;

        Grid(int x, int y) {
            this.x = x;
            this.y = y;
        }

        void initGrid(Grid parent, Grid end) {
            this.parent = parent;
            this.g = (parent == null ? 1 : parent.g + 1);
            this.h = Math.abs(this.x - end.x)
                    + Math.abs(this.y - end.y);
            this.f = this.g + this.h;
        }
    }

    /**
     * 寻路算法主方法
     *
     * @param start
     * @param end
     * @return
     */
    public static Grid search(Grid start, Grid end) {
        // 可到达的格子
        List<Grid> openList = new ArrayList<>();
        // 已到达的格子
        List<Grid> closeList = new ArrayList<>();
        openList.add(start);
        while (openList.size() > 0) {
            // 找到F值最小的格子,作为当前走的路径格子
            // 从OpenList中移除,添加进CloseList中
            Grid curGrid = findMinGrid(openList);
            openList.remove(curGrid);
            closeList.add(curGrid);

            // 找到可达的邻居格子
            List<Grid> neighbors = findNeighbors(curGrid, openList, closeList);
            for (Grid grid : neighbors) {
                if (!openList.contains(grid)) {
                    // 初始化父节点信息,以及计算F,G,H值
                    grid.initGrid(curGrid, end);
                    openList.add(grid);
                }
            }

            // 如果OpenList中存在终点,那么直接返回
            Grid endGrid = null;
            if((endGrid = containGrid(openList, end.x, end.y)) != null) {
                return endGrid;
            }
        }
        return null;
    }

    /**
     * 在OpenList中寻找F值最小的Grid
     *
     * @param openList
     * @return
     */
    private static Grid findMinGrid(List<Grid> openList) {
        Grid minGrid = openList.get(0);
        for (Grid grid : openList) {
            if (grid.f < minGrid.f) {
                minGrid = grid;
                break;
            }
        }
        return minGrid;
    }

    private static List<Grid> findNeighbors(Grid curGrid, List<Grid> openList, List<Grid> closeList) {
        List<Grid> gridList = new ArrayList<>();
        if (isValidGrid(curGrid.x, curGrid.y+1, openList, closeList)) {
            gridList.add(new Grid(curGrid.x, curGrid.y+1));
        }
        if (isValidGrid(curGrid.x+1, curGrid.y, openList, closeList)) {
            gridList.add(new Grid(curGrid.x+1, curGrid.y));
        }
        if (isValidGrid(curGrid.x, curGrid.y-1, openList, closeList)) {
            gridList.add(new Grid(curGrid.x, curGrid.y-1));
        }
        if (isValidGrid(curGrid.x-1, curGrid.y, openList, closeList)) {
            gridList.add(new Grid(curGrid.x-1, curGrid.y));
        }
        return gridList;
    }

    private static boolean isValidGrid(int x, int y, List<Grid> openList, List<Grid> closeList) {
        // 越界
        if (x < 0 || y < 0 || x >= MAZE.length || y >= MAZE[0].length) {
            return false;
        }

        // 围墙
        if (MAZE[x][y] == 1) {
            return false;
        }

        if (containGrid(openList, x, y) != null || containGrid(closeList, x, y) != null) {
            return false;
        }

        return true;
    }

    /**
     * 判断gridList中是否存在 Grid(x, y),
     * 如果存在则直接返回
     * 否则返回 null
     * @param gridList
     * @param x
     * @param y
     * @return
     */
    private static Grid containGrid(List<Grid> gridList, int x, int y) {
        for(Grid grid : gridList) {
            if(grid.x == x && grid.y == y) {
                return grid;
            }
        }
        return null;
    }

    public static void main(String[] args) {
        Grid start = new Grid(2, 1);
        Grid end = new Grid(2, 5);
        Grid result = search(start, end);

        List<Grid> path = new ArrayList<>();
        while(result != null) {
            path.add(new Grid(result.x, result.y));
            result = result.parent;
        }

        for(int i = 0 ; i < MAZE.length ; i++) {
            for(int j = 0 ; j < MAZE[i].length ; j++) {
                if(MAZE[i][j] == 1) {
                    System.out.print("1 ");
                } else if(containGrid(path, i, j) != null) {
                    System.out.print("* ");
                } else {
                    System.out.print("0 ");
                }
            }
            System.out.println();
        }
    }
}

实际上,这个算法有个地方可以优化;我们看到findMinGrid函数,它是找到集合中F值最小的方格,我们每次调用它都得遍历整个集合,算法是线性增加的,并且随着路径的扩大,这个集合的大小不断扩大,算法效率会很低,搜索时间很令人担忧

既然我们每次都取F值最小的元素,那么,可以想到,使用一个最小堆来存储这些元素。这样,算法时间复杂度呈2的指数级下降;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值