A*搜索算法

前言

这个我也不知道算不算是A*搜索算法,可能只是A搜索算法。

首先看相关的定义:

启发式搜索在搜索过程中根据启发信息评估各个节点的重要性,优先搜索重要的节点。

估价函数的任务就是估计待搜索节点“有希望”的程度。

估价函数f(n)定义为从初始节点经过节点n到达目的节点的路径的最小代价估计值,其中一般形式是

f(n)=g(n)+h(n)

g(n)是从初始节点到节点n的实际代价,而h(n)是从节点n到目的节点的最佳路径的估计价值。

在这篇博客中就把事件进行简化了,g(n)表示当前走过的步数,h(n)估计代价用曼哈顿距离来表示。

参考链接:

人工智能大作业——A*算法迷宫寻路问题_a*算法实现迷宫寻路功能-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/weixin_46037153/article/details/107136560A*搜索算法(A-Star Search)简介及保姆级代码解读 (qq.com)icon-default.png?t=N7T8https://mp.weixin.qq.com/s?__biz=MzU1NjEwMTY0Mw==&mid=2247554483&idx=1&sn=c3e6a3259f1b469eded07a5498790186&chksm=fbc86cd7ccbfe5c12f4dddf139f73cb5c2879044867bced71edb0243b155f3d68eda50d0db6a&scene=27机器人路径规划算法(十一)A-star算法 - Mronne's Blogicon-default.png?t=N7T8https://mronne.github.io/2020/04/03/%E6%9C%BA%E5%99%A8%E4%BA%BA%E8%B7%AF%E5%BE%84%E8%A7%84%E5%88%92%E7%AE%97%E6%B3%95-%E5%8D%81%E4%B8%80-A-star-%E7%AE%97%E6%B3%95.html

思路讲解

前期准备

用MAP二维数组来表示待搜索的地图,其中1表示道路受阻碍,0表示通路。

定义Grid来表示地图中的节点,其中x,y表示坐标,fn,hn,gn是之后需要用到的估计值。

定义两个集合来储存新加入的节点以及走过的节点,我这里用到的是链表,因为添加和删除元素比较多。

   //定义地图,直接利用二维数组来实现(0表示可以通过,1表示不能通过
    public static int[][] MAP = {
            {0, 1, 0, 0, 0, 0, 0},
            {0, 0, 1, 0, 0, 0, 0},
            {1, 0, 0, 1, 0, 0, 0},
            {0, 1, 0, 1, 1, 1, 1},
            {0, 0, 0, 1, 0, 0, 0},
            {0, 1, 0, 0, 0, 0, 0},
            {0, 0, 0, 1, 1, 1, 0},
    };

    //把方格抽象成一个类
    public static class Grid {
        private int x;//横坐标
        private int y;//纵坐标
        //fn=hn+gn
        int fn;//估计函数
        int hn;//估计代价
        int gn;//实际代价

        private Grid present;//当前节点

        //构造方法
        public Grid(int x, int y) {
            this.x = x;
            this.y = y;
        }


        //实例化一个方格节点
        public void initGrid(Grid present, Grid end) {
            this.present = present;

            //计算gn
            if (present != null) {
                //实际的代价加一相当于前进了一步
                this.gn = present.gn + 1;
            } else {
                this.gn = 1;
            }

            //计算hn的大小(这里用的估计代价是曼哈顿距离
            this.hn = Math.abs(this.x - end.x) + Math.abs(this.y - end.y);

            //计算fn的大小
            this.fn = this.gn + this.hn;
        }

        public String toString() {
            return "(" + this.x + "," + this.y + ")" + "fn:" + this.fn + " gn:" + this.gn + " hn:" + this.hn;
        }

        @Override
        public int hashCode() {
            int tmp = (this.y + (this.x + 1) / 2);
            return x + (tmp * tmp);
        }

        //重写equals方法,不然比较的是地址值
        @Override
        public boolean equals(Object obj) {
            if (this == obj) return true;
            if (obj == null || getClass() != obj.getClass()) return false;
            Grid other = (Grid) obj;
            if (this.x == other.x && this.y == other.y) {
                return true;
            }
            return false;
        }
    }

 

    //准备两个链表来储存需要选择的节点以及已经走过的节点
    public static LinkedList selectList = new LinkedList();
    public static LinkedList walkList = new LinkedList();
    public static HashMap resultMap = new HashMap();

查找最小估价值的节点

 方法的作用是返回selectList集合中的fn最小的节点。(fn表示最小代价估计值)

  //返回最小的那个节点
    private static Grid findMinGrid(LinkedList selectList) {

        Grid tmpgrid = selectList.get(0);
        for (Grid grid : selectList) {
            //更新最小节点
            if (grid.fn < tmpgrid.fn) {
                tmpgrid = grid;
            }
        }
        return tmpgrid;
    }

判断当前节点是否已经添加

 判断(x,y)坐标是否在grids集合中,如果相等,表示已经添加到集合中了。

  //判断当前的节点是否已经添加
    private static boolean contains(LinkedList grids, int x, int y) {
        for (Grid grid : grids) {
            //坐标在grids集合中有就是已经被添加了
            if ((grid.x == x) && (grid.y == y)) {
                return true;
            }
        }
        return false;
    }

 判断当前的节点是否合法

判断当前节点的坐标是否越界,以及节点是否已经被添加到集合中了,如果已经被添加了就不用管了。

//判断当前节点是否合法
    private static boolean legitimacy(int x, int y, LinkedList selectList, LinkedList walkList) {
        //判断坐标是否越界
        if (x < 0 || x >= MAP.length || y < 0 || y >= MAP[0].length) {
            return false;
        }
        //判断当前节点是否是障碍
        if (MAP[x][y] == 1) {
            return false;
        }
        //判断当前节点是否被添加
        if (contains(selectList, x, y)) {
            return false;
        }
        //判断当前节点是否已经走过了
        if (contains(walkList, x, y)) {
            return false;
        }
        //所以条件都满足,他就是合法的
        return true;
    }

 添加相邻的节点

他的思路就是先找出目前fn最小的那个节点,然后对那个节点相邻的节点进行判断,如果相邻的节点是合法的,就把节点添加到集合中。

 //把所有符合要求的相邻节点都放置到list集合里面里面
    private static LinkedList findNeighbors(Grid grid, LinkedList selectList, LinkedList walkList) {
        LinkedList list = new LinkedList();
        //判断相邻节点的合法性
        if (legitimacy(grid.x, grid.y - 1, selectList, walkList)) {//下(用数组来看的话他就是往上)
            list.add(new Grid(grid.x, grid.y - 1));
        }
        if (legitimacy(grid.x, grid.y + 1, selectList, walkList)) {//上
            list.add(new Grid(grid.x, grid.y + 1));
        }
        if (legitimacy(grid.x - 1, grid.y, selectList, walkList)) {//左
            list.add(new Grid(grid.x - 1, grid.y));
        }
        if (legitimacy(grid.x + 1, grid.y, selectList, walkList)) {//右
            list.add(new Grid(grid.x + 1, grid.y));
        }
        return list;
    }

 算法实现

先把start节点最开始添加到集合中,然后不断的扩展他,知道集合中没有节点,或者找到终点节点表示结束。用当前节点作为值,邻居节点作为键加入到HashMap中,HashMap和TreeMap的储存结构不同,我用TreeMap来储存结果导致键和值都有,直接用keySet()和values()他是有的,但是用containsKey()以及get()传入键返回的是null,这个可能和他们的储存方式不同有关,这里我研究了半天也不知道是什么原因。

深入理解HashMap和TreeMap的区别_treemap和hashmap区别-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/superfjj/article/details/105886065

    //A*算法的实现(需要开始位置和结束位置
    private static void algorithmImplementation(Grid start, Grid end) {

        //将起点加入链表之后开始寻路
        selectList.add(start);

        //链表不为空(没有最后没有到达终点也会停止)
        while (selectList.size() > 0) {
            //找到在需要选择的节点中最小的那个节点,之后需要用它进行扩展
            Grid nowGrid = findMinGrid(selectList);

            //从中删除最小的那个节点
            selectList.remove(nowGrid);
            //将这个节点添加到已经走过的路径中
            walkList.add(nowGrid);

            //寻找他的相邻节点,把合法的节点都添加进来
            LinkedList neighbors = findNeighbors(nowGrid, selectList, walkList);

            for (Grid grid : neighbors) {
                //判断集合中是否添加了grid节点
                if (!selectList.contains(grid)) {
                    //进行初始化
                    grid.initGrid(nowGrid, end);
                    //添加到待搜索集合中
                    selectList.add(grid);
                    if (grid != null && nowGrid != null) {
                        //子节点作为键,父节点作为值
                        resultMap.put(grid, nowGrid);
                    }
                    //System.out.println(grid + " " + resultMap.get(grid));

                }
            }
            //判断是否可以结束
            for (Grid grid : selectList) {
                if ((grid.x == end.x) && (grid.y == end.y)) {
                    walkList.add(end);
                    return;
                }
            }
        }
    }

 主方法

对储存的节点进行打印,走过的道路用2来表示,最终的路径用3来表示。

//主方法
    public static void main(String[] args) {
        System.out.println("原始地图是:");
        for (int i = 0; i < MAP.length; i++) {
            for (int j = 0; j < MAP[0].length; j++) {
                System.out.print(MAP[i][j] + "\t");
            }
            System.out.println();
        }

        Grid grid1 = new Grid(0, 0);
        Grid grid2 = new Grid(MAP.length - 1, MAP[0].length - 1);
        //调用A*搜索算法
        algorithmImplementation(grid1, grid2);


        //走过的路径用2表示
        for (Grid grid : walkList) {
            MAP[grid.x][grid.y] = 2;
        }

    
        while (true) {
            MAP[grid2.x][grid2.y] = 3;
            Grid grid = resultMap.get(grid2);
            grid2 = grid;

            if (grid2 == null) {
                break;
            }

            //到达起点
            if (grid2.x == grid1.x && grid2.y == grid1.y) {
                MAP[grid2.x][grid2.y]=3;
                break;
            }
        }

    

      
        System.out.println("搜索后的地图是:");
        for (int i = 0; i < MAP.length; i++) {
            for (int j = 0; j < MAP[0].length; j++) {
                System.out.print(MAP[i][j] + "\t");
            }
            System.out.println();
        }
    }

Java

import java.util.*;

public class AStarSearch {
    //主方法
    public static void main(String[] args) {
        System.out.println("原始地图是:");
        for (int i = 0; i < MAP.length; i++) {
            for (int j = 0; j < MAP[0].length; j++) {
                System.out.print(MAP[i][j] + "\t");
            }
            System.out.println();
        }

        Grid grid1 = new Grid(0, 0);
        Grid grid2 = new Grid(MAP.length - 1, MAP[0].length - 1);
        //调用A*搜索算法
        algorithmImplementation(grid1, grid2);


        //走过的路径用2表示
        for (Grid grid : walkList) {
            MAP[grid.x][grid.y] = 2;
        }

        /*for (Grid grid : selectList) {
            MAP[grid.x][grid.y] = 4;
        }*/
        while (true) {
            MAP[grid2.x][grid2.y] = 3;
            Grid grid = resultMap.get(grid2);
            grid2 = grid;

            if (grid2 == null) {
                break;
            }

            //到达起点
            if (grid2.x == grid1.x && grid2.y == grid1.y) {
                MAP[grid2.x][grid2.y]=3;
                break;
            }
        }


        /*for(Iteratorit=resultMap.keySet().iterator();it.hasNext();){
            Grid grid=resultMap.get(it.next());
            if(grid!=null){
                System.out.println(grid);
            }
            //MAP[grid.x][grid.y]=3;
        }*/

        //System.out.println(resultMap.keySet());
        //System.out.println(resultMap.values());

       /* Grid grid = new Grid(1, 0);
        grid.fn = 6;
        grid.gn = 1;
        grid.hn = 5;
        if (resultMap.keySet().contains(grid)) {
            System.out.println("有");
        } else {
            System.out.println("没有");
        }

        if(resultMap.containsKey(grid)){
            System.out.println("有");
        }else {
            System.out.println("没有");
        }*/

       /* if(grid.equals(new Grid(1,0))){
            System.out.println("相等");
        }else {
            System.out.println("不相等");
        }
        System.out.println(grid);
        System.out.println(resultMap.get(grid));
        */
        System.out.println("搜索后的地图是:");
        for (int i = 0; i < MAP.length; i++) {
            for (int j = 0; j < MAP[0].length; j++) {
                System.out.print(MAP[i][j] + "\t");
            }
            System.out.println();
        }
    }

    //定义地图,直接利用二维数组来实现(0表示可以通过,1表示不能通过
    public static int[][] MAP = {
            {0, 1, 0, 0, 0, 0, 0},
            {0, 0, 1, 0, 0, 0, 0},
            {1, 0, 0, 1, 0, 0, 0},
            {0, 1, 0, 1, 1, 1, 1},
            {0, 0, 0, 1, 0, 0, 0},
            {0, 1, 0, 0, 0, 0, 0},
            {0, 0, 0, 1, 1, 1, 0},
    };

    //把方格抽象成一个类
    public static class Grid {
        private int x;//横坐标
        private int y;//纵坐标
        //fn=hn+gn
        int fn;//估计函数
        int hn;//估计代价
        int gn;//实际代价

        private Grid present;//当前节点

        //构造方法
        public Grid(int x, int y) {
            this.x = x;
            this.y = y;
        }


        //实例化一个方格节点
        public void initGrid(Grid present, Grid end) {
            this.present = present;

            //计算gn
            if (present != null) {
                //实际的代价加一相当于前进了一步
                this.gn = present.gn + 1;
            } else {
                this.gn = 1;
            }

            //计算hn的大小(这里用的估计代价是曼哈顿距离
            this.hn = Math.abs(this.x - end.x) + Math.abs(this.y - end.y);

            //计算fn的大小
            this.fn = this.gn + this.hn;
        }

        public String toString() {
            return "(" + this.x + "," + this.y + ")" + "fn:" + this.fn + " gn:" + this.gn + " hn:" + this.hn;
        }

        @Override
        public int hashCode() {
            int tmp = (this.y + (this.x + 1) / 2);
            return x + (tmp * tmp);
        }

        //重写equals方法,不然比较的是地址值
        @Override
        public boolean equals(Object obj) {
            if (this == obj) return true;
            if (obj == null || getClass() != obj.getClass()) return false;
            Grid other = (Grid) obj;
            if (this.x == other.x && this.y == other.y) {
                return true;
            }
            return false;
        }
    }

   /* //对Grid类进行判断
    static class GridComparator implements Comparator {

        @Override
        public int compare(Grid grid1, Grid grid2) {
            if (grid1.x == grid2.x && grid1.y == grid2.y) {
                return 0;
            } else {
                return 1;
            }
        }
    }*/

    //准备两个链表来储存需要选择的节点以及已经走过的节点
    public static LinkedList selectList = new LinkedList();
    public static LinkedList walkList = new LinkedList();
    public static HashMap resultMap = new HashMap();
  /*  public static TreeMap resultMap = new TreeMap(new Comparator() {
        @Override
        public int compare(Grid o1, Grid o2) {
            //用坐标标识就行了
            if (o1.x == o2.x && o1.y == o2.y) {
                return 0;
            } else {
                return 1;
            }
        }
    });*/

    //A*算法的实现(需要开始位置和结束位置
    private static void algorithmImplementation(Grid start, Grid end) {

        //将起点加入链表之后开始寻路
        selectList.add(start);

        //链表不为空(没有最后没有到达终点也会停止)
        while (selectList.size() > 0) {
            //找到在需要选择的节点中最小的那个节点,之后需要用它进行扩展
            Grid nowGrid = findMinGrid(selectList);

            //从中删除最小的那个节点
            selectList.remove(nowGrid);
            //将这个节点添加到已经走过的路径中
            walkList.add(nowGrid);

            //寻找他的相邻节点,把合法的节点都添加进来
            LinkedList neighbors = findNeighbors(nowGrid, selectList, walkList);

            for (Grid grid : neighbors) {
                //判断集合中是否添加了grid节点
                if (!selectList.contains(grid)) {
                    //进行初始化
                    grid.initGrid(nowGrid, end);
                    //添加到待搜索集合中
                    selectList.add(grid);
                    if (grid != null && nowGrid != null) {
                        //子节点作为键,父节点作为值
                        resultMap.put(grid, nowGrid);
                    }
                    //System.out.println(grid + " " + resultMap.get(grid));

                }
            }
            //判断是否可以结束
            for (Grid grid : selectList) {
                if ((grid.x == end.x) && (grid.y == end.y)) {
                    walkList.add(end);
                    return;
                }
            }
        }
    }

    //把所有符合要求的相邻节点都放置到list集合里面里面
    private static LinkedList findNeighbors(Grid grid, LinkedList selectList, LinkedList walkList) {
        LinkedList list = new LinkedList();
        //判断相邻节点的合法性
        if (legitimacy(grid.x, grid.y - 1, selectList, walkList)) {//下(用数组来看的话他就是往上)
            list.add(new Grid(grid.x, grid.y - 1));
        }
        if (legitimacy(grid.x, grid.y + 1, selectList, walkList)) {//上
            list.add(new Grid(grid.x, grid.y + 1));
        }
        if (legitimacy(grid.x - 1, grid.y, selectList, walkList)) {//左
            list.add(new Grid(grid.x - 1, grid.y));
        }
        if (legitimacy(grid.x + 1, grid.y, selectList, walkList)) {//右
            list.add(new Grid(grid.x + 1, grid.y));
        }
        return list;
    }

    //判断当前节点是否合法
    private static boolean legitimacy(int x, int y, LinkedList selectList, LinkedList walkList) {
        //判断坐标是否越界
        if (x < 0 || x >= MAP.length || y < 0 || y >= MAP[0].length) {
            return false;
        }
        //判断当前节点是否是障碍
        if (MAP[x][y] == 1) {
            return false;
        }
        //判断当前节点是否被添加
        if (contains(selectList, x, y)) {
            return false;
        }
        //判断当前节点是否已经走过了
        if (contains(walkList, x, y)) {
            return false;
        }
        //所以条件都满足,他就是合法的
        return true;
    }

    //判断当前的节点是否已经添加
    private static boolean contains(LinkedList grids, int x, int y) {
        for (Grid grid : grids) {
            //坐标在grids集合中有就是已经被添加了
            if ((grid.x == x) && (grid.y == y)) {
                return true;
            }
        }
        return false;
    }

    //返回最小的那个节点
    private static Grid findMinGrid(LinkedList selectList) {

        Grid tmpgrid = selectList.get(0);
        for (Grid grid : selectList) {
            //更新最小节点
            if (grid.fn < tmpgrid.fn) {
                tmpgrid = grid;
            }
        }
        return tmpgrid;
    }


}

A*搜索算法_第1张图片

总结

现在只写了Java的代码,之后会有时间会用其他语言写,之前问了老师,他说用Python可以实现图形化的那种,但是面前还不太会,以后有时间就会更新一下这篇博客。

9月27日补充,这个算是A*搜索算法,因为他这个迷宫的走法就是上下左右,不能够斜着走,所以曼哈顿距离就是最小的代价。但是目前的缺点是图太简单了,而且没有实现可视化,也没有把最短的路径标记出来,只是把走过的路标记了起来,所以还没有达到目标的要求,这个问了老师说需要用到回溯的思想,但是目前还没实现。

你可能感兴趣的:(java,开发语言)