用java实现A*寻路算法

前言:

最近的开发中遇到了寻路这个知识点,然后去了解了一下最常见的A算法,本会会结合我的理解,用最通俗易懂的话语讲解A算法的原理,下面会给出代码示例。

说到寻路算法,就涉及到了图的遍历,然后又分为深度优先和广度优先等等,这些知识点大家随便一搜就能查到,为了方便这里给个链接,里面有介绍算法的发展史。

在A出来之前,遍历算法要不就是效率不高,要不就是不是最优路径,A的出现,是结合了前面的有点,所以说A*只是相对来说效率很高的一种算法。

A*算法

A*算法通过下面这个函数来计算每个节点的优先级。
算法表示:
在这里插入图片描述
其中:

  • f(n)是节点n的综合优先级。当我们选择下一个要遍历的节点时,我们总会选取综合优先级最高(值最小)的节点。
  • g(n) 是节点n距离起点的代价。本文中,会以父子节点距离相加来算距离起点的距离。
  • h(n)是节点n距离终点的预计代价,这也就是A*算法的启发函数。关于启发函数我们在下面详细讲解。

一句话来解释就是:比如从A到C,中间有个点B,A算法会计算B离A的距离和B离C的距离的综合值来判定,顾前又顾后。
A
算法在运算过程中,每次从优先队列中选取f(n)值最小(优先级最高)的节点作为下一个待遍历的节点。

关键集合: 有两个集合openList和closeList,openList存放待遍历的坐标点,closeList存放遍历过的坐标点,closeList最后也用来表示路径。

如果用文字来描述A*算法:

  1. 将起始点加入openList。
  2. while循环,只要openList不包含目标点(如果包含了说明已经找到路径了),从opelist取出一个f(n)最小值(第一次是起始点)的点A,然后将点A周围的点,计算好f(n),放入openList。
  3. openList删除点A。
  4. closeList加入点A。
  5. 输入路径。

为了便于第二步取f(n)最小的点,openList和closeList,我都用的PriorityQueue优先级队列,里面规则就是f(n)最小的在最顶端。

然后简单说一下启发函数,对于网格形式的图,有以下这些启发函数可以使用:

  • 如果图形中只允许朝上下左右四个方向移动,则可以使用曼哈顿距离(Manhattan distance)。
  • 如果图形中允许朝八个方向移动,则可以使用对角距离。
  • 如果图形中允许朝任何方向移动,则可以使用欧几里得距离(Euclidean distance)。

本文中使用的欧几里得距离,也就是我们上学学的,两点坐标相减的平方,再开根号
在这里插入图片描述

代码部分:

我们先看看效果图:
我们随机生成一个20x15的地图,其中0表示可走,* 代表是障碍物不可走,起点A和终点B都是随机生成的。
在这里插入图片描述
然后看看算法给出的路径:这里路径用红色底色标出了。
在这里插入图片描述
其实这样一看,A*算法的步数少,时间也用的少,效率高。

下面就开始说下代码部分的思路,首先我们约定几点关键内容:

  1. 约定每一个格子的距离是1,这样算,如果A和B是平行的、挨着的两个点,那么他们距离相距1,如果A和B是斜对角的点,那么就是根号2,约定于1.4几,我们取1.4,为了没有小数,我们代码中在计算的时候都乘以10,所以就是10和14(代码中有这样的计算方式)。
  2. 我们用Node类来表示地图上每一个坐标点,每个Node都有parentNode,parentNode的用处就是用来计算g(n),通过累加来计算出当前点离起始点的g(n)值

代码主要有3个类,

  • Map.java,地图生成类,主要用来生成地图坐标和起始点。
  • Node.java,地图坐标的包装类,里面存了一些信息。
  • AStar.java,寻路算法类,里面是寻路算法的实现。

下面贴上所有类的代码,里面有关键注释。我们先从入口代码开始看:

public class MainTest {
    public static void main(String[] args) {
        Map map = new Map();
        map.ShowMap(null);
        AStar aStar = new AStar();
        aStar.search(map);
    }
}

Node.java

/**
 * 地图节点类
 */
@Data
public class Node {
    //坐标的x和y
    private int x;
    private int y;
    //value:0表示可通过,*表示障碍物
    private String value;
    //这三个参数就是f(n)=g(n)+h(n)
    private double fValue = 0;
    private double gValue = 0;
    private double hValue = 0;
    //标注当前节点是否走过
    private boolean reachable;
    private Node parentNode;

    public Node(int x, int y, String value, boolean reachable) {
        this.x = x;
        this.y = y;
        this.value = value;
        this.reachable = reachable;
    }

    public Node() {
    }
}

Map.java

/**
 * 地图类
 */
@Data
public class Map {
    public static final int Length = 15;
    public static final int Weight = 20;
    private Node[][] map;
    private Node start;
    private Node end;

    public Map() {
        map = new Node[Length][Weight];
        Random random = new Random();
        int startX = random.nextInt(Length - 2) + 1;
        int startY = random.nextInt(Weight - 2) + 1;
        int endX = random.nextInt(Length - 2) + 1;
        int endY = random.nextInt(Weight - 2) + 1;
        System.out.println("起点坐标:" + startX + "," + startY);
        System.out.println("目标点坐标:" + endX + "," + endY);
        for (int i = 0; i < Length; i++) {
            for (int j = 0; j < Weight; j++) {
                Node node = new Node(i, j, "0", true);
                if (i == 0 || j == 0 || i == Length - 1 || j == Weight - 1) {
                    node.setValue("*");
                    node.setReachable(false);
                } else {
                    int num = random.nextInt(10);
                    if (num < 3) {
                        node.setValue("*");
                        node.setReachable(false);
                    }
                    if (i == startX && j == startY) {
                        node.setValue("A");
                        node.setReachable(true);
                        start = node;
                    }
                    if (i == endX && j == endY) {
                        node.setValue("B");
                        node.setReachable(true);
                        end = node;
                    }
                }
                map[i][j] = node;
            }
        }
    }

    public void ShowMap(PriorityQueue<Node> nodeList) {
        System.out.print("\\" + "   ");
        for (int i = 0; i < Weight; i++) {
            System.out.print(i + "   ");
        }
        System.out.println();
        for (int i = 0; i < Length; i++) {
            for (int j = 0; j < Weight; j++) {
                Node node = map[i][j];
                String value = map[i][j].getValue();
                if (nodeList == null) {
                    if ("A".equals(value) || "B".equals(value)) {
                        System.out.format("\33[41;4m" + value + "\33[0m" + "   ");
                    } else {
                        if (j == 0) {
                            System.out.print(i + "   ");
                            System.out.print(value + "   ");
                        } else {
                            System.out.print(value + "   ");
                        }
                    }
                } else {
                    if (j == 0) {
                        System.out.print(i + "   ");
                    }
                    if (nodeList.contains(node)) {
                        System.out.format("\33[41;4m" + value + "\33[0m" + "   ");
                    } else {
                        System.out.print(value + "   ");
                    }
                }
            }
            System.out.println();
        }
    }
}

Astar,java

public class AStar {
    private final PriorityQueue<Node> openList = new PriorityQueue<>((o1, o2) -> (int) (o1.getFValue() - o2.getFValue()));
    private final PriorityQueue<Node> closeList = new PriorityQueue<>((o1, o2) -> (int) (o1.getFValue() - o2.getFValue()));
    private Map map;

    public void search(Map mapData) {
        this.map = mapData;
        Node start = map.getStart();
        openList.add(start);
        while (!openList.contains(mapData.getEnd())) {
            Node node = openList.peek();
            assert node != null;
            selectNearByNode(node);
            node.setReachable(false);
            openList.remove(node);
            closeList.add(node);
        }
        closeList.add(mapData.getEnd());
        showPath(closeList, map);
    }

    /**
     * 将选中节点周围的节点添加进“开启列表”
     */
    public void selectNearByNode(Node currentNode) {
        int x = currentNode.getX();
        int y = currentNode.getY();
        for (int dx = (x > 0 ? -1 : 0); dx <= (x < Map.Length ? 1 : 0); ++dx) {
            for (int dy = (y > 0 ? -1 : 0); dy <= (y < Map.Weight ? 1 : 0); ++dy) {
                if (dx != 0 || dy != 0) {
                    Node node = map.getMap()[x + dx][y + dy];
                    if (node.isReachable() && !openList.contains(node)) {
                        node.setParentNode(currentNode);
                        node.setGValue(getGValue(node));
                        node.setHValue(getHValue(node, map.getEnd()));
                        node.setFValue(getFValue(node));
                        openList.add(node);
                    }
                }
            }
        }
    }

    /**
     * 获取H值
     *
     * @param currentNode:当前节点
     * @param endNode:终点
     */
    public double getHValue(Node currentNode, Node endNode) {
        return (Math.abs(currentNode.getX() - endNode.getX()) + Math.abs(currentNode.getY() - endNode.getY())) * 10;
    }

    /**
     * 获取G值
     *
     * @param currentNode:当前节点
     */
    public double getGValue(Node currentNode) {
        if (currentNode.getParentNode() != null) {
            if (currentNode.getX() == currentNode.getParentNode().getX() || currentNode.getY() == currentNode.getParentNode().getY()) {
                //判断当前节点与其父节点之间的位置关系(水平?对角线)
                return currentNode.getParentNode().getGValue() + 10;
            }
            return currentNode.getParentNode().getGValue() + 14;
        }
        return currentNode.getGValue();
    }

    /**
     * 获取F值 : G + H
     */
    public double getFValue(Node currentNode) {
        return currentNode.getGValue() + currentNode.getHValue();
    }


    /**
     * 将路径标记出来
     */
    public void showPath(PriorityQueue<Node> closeList, Map map) {
        if (closeList.size() > 0) {
            System.out.println("路径为:");
            map.ShowMap(closeList);
        }
    }
}

总的来说,A*算法还是很简单的,如果有啥疑问欢迎在评论区询问,如果有错误也欢迎指出。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/437085.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

开年采购云服务器,怎么买最划算?看这篇!

在2024年开年之际&#xff0c;对于许多企业和个人而言&#xff0c;采购云服务器已成为一项重要的决策。云服务器以其灵活性、可扩展性和高可用性等特点&#xff0c;吸引了越来越多的用户。然而&#xff0c;市场上的云服务器提供商众多&#xff0c;如何选择一家值得入手的服务商…

Domain Driven Design (DDD)

Domain Driven Design (DDD领域驱动设计)主要是业务分类例如&#xff08;订单、合同、生产、检测、物流、运输等&#xff09;&#xff0c;独立单元相互不干扰&#xff0c;仅暴露接口的模型。核心在Domain&#xff0c;所有业务模块放这边&#xff0c;当然我们做的时候微服务是一…

如何对接1688平台官方开发平台的商品发布/商品过期处理/商品订单接口?

custom-自定义API操作 API测试 注册开通 1688.custom 1688平台官方开放接口 公共参数 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&#xff09;secretString是调用密钥api_nameString是API接口名称&#xff08;包括在请求地址中&#xff09;[…

windows部署ruoyi-vue-pro

前提 安装java 安装maven 安装redis mysql 源代码下载 后端 ruoyi-vue-pro 前端 yudao-ui-admin-vue3 后端项目 配置maven 导入数据 CREATE DATABASE ruoyi_vue_pro;修改mysql连接配置 修改redis 打包项目 mvn clean install package -Dmaven.test.skiptrue启动YudaoSe…

TC397 Tasking CMake Gitlab CI CD 环境配置

文章目录 Aurix Development Studio 新建工程与配置Tasking 环境配置CMake 集成Win CMake MinGW 安装Tasking Toolchain 工具链CMakeLists.txtPowershell 脚本 Gitlab CI CDGithub Link 本篇先演示了ADS新建激活编译工程, 讲述了浮点模型, 链接脚本文件, 静态库集成等的设置, 接…

python并发编程:IO模型

一 IO模型 二 network IO 再说一下IO发生时涉及的对象和步骤。对于一个network IO \(这里我们以read举例\)&#xff0c;它会涉及到两个系统对象&#xff0c;一个是调用这个IO的process \(or thread\)&#xff0c;另一个就是系统内核\(kernel\)。当一个read操作发生时&#xff…

C语言学习--摩尔投票算法

目录 1.引入 2.摩尔投票算法 3.具体步骤 3.1抵消阶段 3.2检验过程 4.代码实现 5.总结 1.引入 今天做题看到一个解题思路真的看不懂&#xff0c;一艘才知道是这个算法。 int majorityElement(int* nums, int numsSize) { int notenums[0]; int count1; for(int i1;i<n…

day7-网络编程

1>基于UDP的网络聊天室 Ser.c #include <myhead.h> #define SER_IP "10.211.55.9" // 服务器IP #define SER_PORT 9999struct user {char usrName[20];struct sockaddr_in cin; }; int main(int argc, char const *argv[]) {// 1.创建用于监听的套接字int…

coqui-ai/TTS 案例model文件

GitHub - coqui-ai/TTS: &#x1f438;&#x1f4ac; - a deep learning toolkit for Text-to-Speech, battle-tested in research and production Coqui AI的TTS是一款开源深度学习文本转语音工具&#xff0c;以高质量、多语言合成著称。它提供超过1100种语言的预训练模型库&…

Elemenu中el-table中使用el-popover选中关闭无效解决办法

主要是技术太菜,没找到原因,一点点才找到这个办法解决 因为在el-table-column里,因为是多行,使用trigger"manual" 时,用v-model"visible"来控制时,控件找不到这个值,才换成trigger"click" 先找到弹出关闭事件,再找元素的属性 右键>审核元素…

哪款洗地机值得买?希亦、追觅、米博、美的谁才是行业标杆?

在家庭清洁中&#xff0c;最让我们苦恼的便是厨房垃圾了&#xff0c;油渍跟食物残渣&#xff0c;用扫把扫了后&#xff0c;要反反复复的湿拖五六次&#xff0c;期间不停的手洗拖把&#xff0c;这套流程下来&#xff0c;往往容易腰酸背痛&#xff0c;手指皱巴巴的&#xff0c;这…

Java项目:40 springboot月度员工绩效考核管理系统009

作者主页&#xff1a;源码空间codegym 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 本系统的功能分为管理员和员工两个角色 管理员的功能有&#xff1a; &#xff08;1&#xff09;个人中心管理功能&#xff0c;添加管理员账号…

【BUG修复日志】Anaconda + VSCode 编码错误

【BUG修复日志】Anaconda VSCode 编码错误 平台: Windows11家庭版 (v22621.3155) 软件: Visual Studio Code (v1.87.0) 插件: Python (v2024.2.1) 版本: Conda (v24.1.2)问题描述 VSCode 在安装 Python 插件的情况下自动提示配置 Conda 环境&#xff0c;但是在自动配置完成后…

SpringMVC实用技术

1.校验框架 1.表单校验框架入门 表单校验的重要性 数据可以随意输入&#xff0c;导致错误的结果。表单校验保障了数据有效性、安全性 表单校验分类 校验位置&#xff1a; 客户端校验 服务端校验 校验内容与对应方式&#xff1a; 格式校验 客户端&#xff1a;使用Js技术…

【linuxC语言】系统调用IO文件操作

文章目录 前言一、文件描述符介绍二、系统调用IO API介绍2.1 open函数2.2 close函数2.3 read函数2.4 write函数2.5 lseek函数 三、示例代码总结 前言 在Linux系统中&#xff0c;C语言通过系统调用实现对文件的输入输出&#xff08;I/O&#xff09;操作。系统调用提供了访问操作…

LLM - 使用 Langchain 实现本地 Naive RAG

目录 一.引言 二.构建本地 Langchain 库 1.Doc 知识文档 2.Split 文档切分 3.Encode 内容编码 4.Similar 本地库构建 三.缓存本地 Langchain 库 四.读取本地 Langchain 库 1.Load 读取缓存 2.Similar 预测 3.Add 添加文档 五.总结 一.引言 上一篇博客介绍了当下 R…

M2TS转MP4怎么转?超快的方法~

M2TS格式的优点主要体现在对高清视频的完美支持&#xff0c;能够提供极致的视觉体验。然而&#xff0c;由于其相对较大的文件大小&#xff0c;有时可能不太适合网络传输。此外&#xff0c;部分不支持M2TS的播放设备可能导致一定的兼容性问题。 想要播放m2ts视频&#xff0c;可…

MySQL实战45讲——30答疑文章(二):用动态的观点看加锁

目录 不等号条件里的等值查询 等值查询的过程 怎么看死锁&#xff1f; 怎么看锁等待&#xff1f; update 的例子 小结 上期问题时间 提示 文章摘自林晓斌老师《MySQL实战45讲》&#xff0c;作为笔记而用&#xff0c;故有加一些自己的理解。在第[20]和[21]篇文章中&…

Python基础二

一、变量 在编程中&#xff0c;变量是用来存储数据值的名称。在 Python 中&#xff0c;变量是动态类型的&#xff0c;这意味着你可以将任何类型的数据分配给一个变量&#xff0c;而不需要提前声明变量的类型。 1、全局变量 在函数外部定义的变量是全局变量&#xff0c;可以在程…

cesium-天际线

主要是两个着色器 let postProccessStage new Cesium.PostProcessStage({//unform着色器对象 textureScalefragmentShader:// 声明一个纹理采样器 colorTexture 用于读取纹理颜色uniform sampler2D colorTexture; // 声明一个纹理采样器 depthTexture 用于读取深度纹理unifor…
最新文章