A-star 算法原理分析

2023-05-16

搜索算法

图论中,应用最广泛的就是搜索算法了,比如,深度优先搜索、广度优先搜索等。在介绍 Dijkstra 算法那篇中,除了深度优先、广度优先这种暴力搜索算法,还有一些最短路算法也可以求得最短路径,并且效率比深度、广度优先搜索高。

在一些大型网络游戏中,往往存在一种场景,游戏中的你需要从当前位置走到目的地,而你只需要在地图中找到目标位置,点击即可自动寻路走过去,那么这个自动寻路的功能是怎么实现的呢?

在实际场景中,游戏地图通常会很大,而且其中的路,障碍物等都不是很规则,并不能简单的将它们抽象成节点和线段。在寻路的过程中需要绕过所有障碍物,并且找到一条不会绕路的路径。似乎最短路是最符合要求的,但是如果地图很大,节点很多很多,那么求最短路的算法执行效率还是太低了。

不管是游戏中的寻路还是我们日常生活中地图软件的路线规划,其实都不需要找到那条最短路,而是在权衡效率和路线质量的情况下,找到一个次优解即可,A* 算法就是可以平衡效率和路线质量,找到次优路线的搜索算法。

A* 算法

A* 算法实际上是对 Dijkstra 算法的优化和改造后得到的。Dijkstra 算法详见:
单源最短路 Dijkstra 算法原理详解、代码实现和复杂度分析.

Dijkstra 算法的核心思想是每次都获取离起始点最近的节点,再向外扩展。

在这里插入图片描述

如图,从起始点 1 开始,目的地为节点 5。如果采用 Dijkstra 算法,首先一定会走节点 6,7,8。但实际上走节点 6,7,8 是在绕路了,这肯定不是最佳路径,即一开始有点跑偏了。考虑一下如何避免一开始跑偏这个问题呢?

在 Dijkstra 算法中,我们创建了优先队列,从优先队列中取出节点,再从这个节点扩散到其他节点(有点类似广度优先搜索),优先队列的优先依据是根据起始点到队列中节点最近的一个。即队列中的节点总是离起始点最近的先出队,这样就很好理解为什么一开始会跑偏了。

当我们走到某个节点时,已知的是起始点到该节点的距离 g(i),Dijkstra 算法判断依据就只有这一个,那么我们是否可以再增加一个判断量来减少跑偏的概率。如果能计算得到当前节点到终点的距离,结合 g(i) 可以实现防止过度跑偏。但是这个 距离是未知的,我们可以计算一个估值,那么如何计算出它的估值呢?

在地图中,我们虽然不知道中间某个点距离终点的最短路,但是我们可以将地图置于坐标轴中,通过计算节点之间的欧几里得距离得到两点之间的直线距离,这便可以作为当前节点离终点距离的估值,欧几里得距离计算需要开平方等复杂的操作,因此我们通过计算简单的曼哈顿距离来代替欧几里得距离,曼哈顿距离就是计算两点之间横纵坐标的距离之和,只涉及到加减法和符号转换。这里得到的距离记为 h(i),称作启发函数

如图,绿色线为欧几里得距离,其他为曼哈顿距离。
在这里插入图片描述

到这里为止,我们就可以得到最终的估价函数啦,优先队列中以估价函数值最低的优先出队。

f(i) = g(i) + h(i)

A* 算法的代码实现

现在,我们要实现 A* 算法的代码,那么首先就需要抽象出节点等的数据结构,与 Dijkstra 算法相似,但加入了横纵坐标值:

public class Graph {

    private class Edge {// 表示边
        public int sid;// 边的起始节点
        public int tid;// 边的结束节点
        public int w;// 边的权重
        
        public Edge(int s, int t, int w) {
            this.sid = s;
            this.tid = t;
            this.w = w;
        }
    }

    private class Vertex {
        public int id; // 顶点编号 ID
        public int dist; // 从起始顶点,到这个顶点的距离,也就是 g(i)
        public int f; // 新增:f(i)=g(i)+h(i)
        public int x, y; // 新增:顶点在地图中的坐标(x, y)
        public Vertex(int id, int x, int y) {
          this.id = id;
          this.x = x;
          this.y = y;
          this.f = Integer.MAX_VALUE;
          this.dist = Integer.MAX_VALUE;
        }
    }


    private LinkedList<Edge> adj[];// 邻接表
    private Vertex[] vertexes;//记录所有顶点的坐标
    private int v;// 顶点数
    
    public Graph(int v) {
        this.v = v;
        Vertex[] vertexes = new Vertex[this.v];
        this.adj = new LinkedList[v];
        for(int i; i<v; i++) {
            this.adj[i] = new LinkedList<Edge>();
        }
    }
    
    public void addEdge(int s, int t, int w) {
        this.adj[s].add(new Edge(s, t, w));
    }
    
    //添加顶点坐标
    public void addVetex(int id, int x, int y) {
        vertexes[id] = new Vertex(id, x, y)
    }
}

以上就是 A* 算法的图的定义,与 Dijkstra 算法的图区别在于:

  1. 图类中增加成员变量Vertex[] vertexes记录所有节点的坐标
  2. 构造函数中进行初始化Vertex[] vertexes
  3. 新增添加节点坐标的方法addVetex(int id, int x, int y)

在具体算法实现中,与 Dijkstra 的区别主要在以下几点:

  1. 优先队列的构建方式不同,A* 算法是使用代码中public int f;这个值来构建的,而 Dijkstra 算法是用 dist 值构建的
  2. A* 算法除了需要更新 dist 值,还需要更新 f 值。
  3. A* 算法循环结束的条件是只要遍历到终点即退出循环,因此 A* 算法最终并不一定能得到最短路径。
public void AStar(Graph g, int s, int t) { // 从顶点 s 到顶点 t 的路径
    int[] predecessor = new int[this.v]; // 用来还原路径
    
    // 按照 vertex 的 f 值构建的小顶堆,而不是按照 dist
    PriorityQueue queue = new PriorityQueue(this.v);
    
    boolean[] inqueue = new boolean[this.v]; // 标记是否已经走过
    
    // 起始节点初始化
    vertexes[s].dist = 0;
    vertexes[s].f = 0;
    queue.add(vertexes[s]);
    inqueue[s] = true;
    
    while (!queue.isEmpty()) {
        Vertex minVertex = queue.poll(); // 取堆顶元素并删除
        for (int i=0; i<g.adj[minVertex.id].size(); i++) {
            Edge e = g.adj[minVertex.id].get(i); // 取出一条 minVetex 相连的边
            Vertex nextVertex = vertexes[e.tid]; // 下一个节点
            if (minVertex.dist + e.w < nextVertex.dist) { // 更新 next 的 dist,f
            
                nextVertex.dist = minVertex.dist + e.w;// 更新 dist
                nextVertex.f = nextVertex.dist+hManhattan(nextVertex, vertexes[t]);// 更新 f 值
                
                // 记录路径
                predecessor[nextVertex.id] = minVertex.id;
                
                //更新队列中的节点信息
                if (inqueue[nextVertex.id] == true) {
                    queue.update(nextVertex);
                } else {
                    queue.add(nextVertex);
                    inqueue[nextVertex.id] = true;
                }
            }
            if (nextVertex.id == t) { // 只要到达终点就退出循环
                queue.clear(); // 清空队列,否则无法退出 while 循环
                break; 
            }
        }
    }
    // 输出路径
    System.out.print(s);
    print(s, t, predecessor);
}

/**
 * 计算曼哈顿距离
 *
 */
int hManhattan(Vertex v1, Vertex v2) {
  return Math.abs(v1.x - v2.x) + Math.abs(v1.y - v2.y);
}

private void print(int s, int t, int[] pre) {
    if(s == t) return;
    print(s, pre[t], pre);
    System.out.print("->"+t);
}

A* 算法实践

在了解 A* 算法的原理后,我们可以尝试着解决游戏中寻路的问题,游戏中的地图,通路弯弯曲曲,障碍物不规则,似乎无法抽象成点与线,这时我们可以将地图分割成一个个极小的方块,从起点开始向其他三个方向走,当然,障碍物所在的方块不能走。方块之间的间距为 1。

这样我们就可以将一个游戏地图抽象成边权值为 1 的有向图,就可以使用 A* 算法来寻路了。

拓展

A* 算法属于**启发式搜索算法(Heuristically Search Algorithm)**的一种,其他属于启发式搜索的算法还有:IDA* 算法、蚁群算法、遗传算法、模拟退火算法等等。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

A-star 算法原理分析 的相关文章

  • 使用vue-router携带不同参数多次push到一个页面时请求 不重新触发问题 ,只有第一次触发

    1 vue跳转 this router push path 39 user userDetils 39 query id JSON stringify val id name JSON stringify this searchData n
  • 惯性导航坐标系介绍

    常用坐标系定义 运载体中三维空间运动包含六个自由度 xff0c 既有角运动也有线运动 在地球表面附近 xff0c 运载体的角运动描述一般以当地水平面和地理北向为参考基准 xff1b 线运动的描述通常采用地理经度 纬度和高度表示 xff0c
  • 达梦 DM管理工具

    DM 管理工具是数据库自带的图形化工具 xff0c 可以方便快捷的对数据进行管理 在网络允许的条件下 xff0c 可通过单个管理工具 xff0c 对多个数据实例进行管理 xff0c 方便简化 DBA 对数据库的日常运维操作要求 打开DM管理
  • Windows 下 DM 的安装 和 数据库配置工具使用说明

    步骤 1 xff1a 运行安装程序 用户将 DM 安装光盘放入光驱中 xff0c 插入光盘后安装程序自动运行或直接双击 setup exe 安装程序后 xff0c 程序将检测当前计算机系统是否已经安装其他版本 DM 如 果存在其他版本 DM
  • window 下 达梦数据库的备份和还原

    DM 提供的各种工具进行备份还原与恢复的操作 xff0c 包括 DIsql工具 DMRMAN 工具 图形化客户端管理工具 MANAGER 和 CONSOLE DIsql 工具用于执 行联机的数据备份与数据还原 xff0c 包括数 归档备份据
  • Linux下与Windows的文件共享

    有三种方法 安装VMware Tools xff08 在虚拟机 gt 重新安装VMware Tools xff09 通过Winscp软件 xff08 前提Windows能ping通linux xff0c 和关防火墙 xff09 本文介绍 x
  • 关于大小端的经典问题

    源代码如下 xff1a span class hljs preprocessor include lt stdio h gt span span class hljs keyword int span main span class hlj
  • 关于#define宏的生命周期

    我们一起来看一段代码 xff1a span class hljs preprocessor include lt stdio h gt span span class hljs preprocessor define X 3 span sp
  • 关于char的溢出问题

    现在看下面的问题 span class hljs keyword int span main span class hljs keyword char span number 61 span class hljs number 129 sp
  • exit()和_exit()的区别

    exit c源代码 xff1a span class hljs preprocessor include span span class hljs preprocessor include span span class hljs keyw
  • ubuntu16.04下u盘的自动挂载(脚本)

    一般固定的u盘在 dev sdxx 的形式 先在 mnt下建一个usb目录用于挂载 1 在 etc udev rules d下创建10 usb rules文件 xff0c 内容如下 xff1a SUBSYSTEM 61 61 34 bloc
  • arp欺骗

    ARP工作的过程 原理及现象 ARP全称是地址解析协议 xff08 address resolution potocol xff09 xff0c 是在仅仅知道主机的IP地址时确定其物理的地址的一种协议 ARP协议的工作过程 场景 xff1a
  • LeetCode中stdout结果是正确的,输出没有

    没有返回值 xff0c 加一行return
  • gstreamer学习(一) gstreamer-rtsp-server环境安装

    gstreamer rtsp server环境安装 Linux环境下 两种方式 xff1a 第一种方式 xff0c 通过官网安装 xff08 如果是Linux环境 xff0c 可以直接通过软件包工具进行安装 xff09 xff0c 点击进入
  • 用C++打开指定网址

    用C 43 43 打开指定网址原理 system 命令 就像这样 xff1a span class token macro property span class token directive hash span span class t
  • 项目遇到的各种异常抛出及解决方法

    项目遇到的各种异常抛出及解决方法 xff1a 1 java lang NumberFormatException xff1a 类型格式异常 第一次遇到的异常抛出原因及解决方法 xff1a 项目运行没有问题 xff0c 各种接口能正常查询出数
  • 【STC8学习笔记】STC8A8K64S4A12精准延时函数设置

    在设置单片机精准的延时函数的时候 xff0c 给大家一个方法 xff0c STC ISP有一个延时函数计算器 xff0c 可以计算出想要的延时 我的例程也是基于这个软件生成的 xff0c 我生成一个1ms和1us出来 xff0c 剩下的我再
  • vc版本与vs版本对应关系

    vc版本与vs版本对应关系 最近在整理之前代码 xff0c 用cmake编译一直报错 xff0c 忘记了opencv3 1 0不支持vs2019 xff0c 所以在这里总结下vc版本与vs版本对应关系 VC版本号 VS对应版本 vc6 VC
  • cmake编译依赖opencv的c++库

    前面一篇主要讲了c 43 43 项目怎么在本地配置opencv过程 xff0c 这种方式缺点就是只能在开发着本地环境编译 xff0c 换台电脑就会出现环境配置问题 接下来主要讲解 xff0c 使用cmake编译 xff0c 生成一个依赖op
  • c++ stl 迭代器iterators(traits编程技法)

    文章目录 1 1 迭代器设计思维 stl关键所在1 2 迭代器是一种smart pointer1 3 迭代器相应型别 xff08 associated types xff09 1 4 traits编程技法 stl源代码门匙1 4 1 val

随机推荐

  • 如何用算法把一个十进制数转为十六进制数-C语言基础

    这一篇文章要探讨的是 如何用算法实现十进制转十六进制 并不涉及什么特别的知识点 属于C语言基础篇 在翻找素材的时候 xff0c 发现一篇以前写的挺有意思的代码 xff0c 这篇代码里面涉及的知识点没有什么好讲的 xff0c 也没有什么特别的
  • 关于 Qt使用QJsonObject解析失败的问题。

    1 问题 在QJsonObject转 toInt toLongLong 等类型时 xff0c 转换失败 但是转toString xff08 xff09 没有任何问题 列如 xff1a 解决方法 xff1a 这样 xff0c 就可以结局问题
  • char 和 string 的相互转换

    一个char字符转为string span class token keyword char span ch span class token operator 61 span span class token char 39 A 39 s
  • C++STL标准库学习总结/索引/学习建议

    前言 xff1a 如果刚刚开始学习STL标准库 xff0c 不知道从哪里入手学习的话 xff0c 建议去中国大学mooc平台 xff0c 先学习北京大学郭炜老师的 程序设计与算法 xff08 一 xff09 C语言程序设计 xff08 ht
  • Python 调用API接口方式,通过http.client调用api接口,远程调用flask接口方式

    一 创建接口 xff08 如果调用别人的接口 xff0c 跳过此条 xff09 如果没有api xff0c 首先自己写一个接口玩一下 xff1a 必备知识 xff1a 一个项目最基本的文件 xff0c 接口run py文件 config文件
  • git tag和branch的区别

    tag 和branch的区别 Git tag是一系列commit的中的一个点 xff0c 只能查看 xff0c 不能移动 branch是一系列串联的commit的线 git tag的用法 我们常常在代码封板时 使用git 创建一个tag 这
  • 结构体对齐计算(超详细讲解,一看就会)

    想要计算结构体大小 xff0c 咱就先要清楚结构体内存对齐的规则 xff1a 1 结构体的第一个成员直接对齐到相对于结构体变量起始位置为0处偏移 2 从第二个成员开始 xff0c 要对齐到某个 对齐数 的整数倍的偏移处 3 结构体的总大小
  • RTK差分编码

    一 概念 DCB xff08 Differential Code Bias 差分码偏差 xff09 是全球卫星导航系统 xff08 GNSS xff09 中 xff0c 通过不同信号得到的观测值之间存在的系统性偏差 DCB是由卫星和接收机硬
  • 详解JAVA的事件监听机制和观察者设计模式

    一 事件监听机制的三要素 事件源 事件监听器 xff0c 事件对象 监听器一般是JAVA接口 xff0c 用来约定可以执行的操作 二 事件监听机制简要说明 事件源注册一个或者多个事件监听器 xff0c 事件源对象状态发生变化或者被操作时 x
  • Nginx控制IP(段)的访问策略配置

    Nginx engine x 是一个高性能的HTTP和反向代理web服务器 xff0c 同时也提供了IMAP POP3 SMTP服务 有着负载均衡 动静分离等强大的功能 xff0c 而且还有众多三方插件来满足应用要求 这里重点介绍nginx
  • 敏捷开发-互联网时代的软件开发方式

    一 什么是敏捷开发 敏捷开发简单的描述为 xff1a 是一种应对需求快速变化的软件开发方式 敏捷开发的核心思想就是小步快跑 不断迭代 xff0c 在一次次的迭代升级中完成 小目标 最终完成那个 大目标 正因为敏捷开发的这种不断迭代升级的开发
  • Window系统查看端口是否启用以及占用程序

    1 打开DOS命令行窗口 开始 gt 运行 gt cmd xff0c 或者是 window 43 R gt cmd xff0c 调出命令窗口 2 查看当前正在使用的所有端口 命令 xff1a netstat ao 包括协议 xff0c 端口
  • ThreadLocal的深度解读

    一 J2SE的原始描述 This class provides thread local variables These variables differ from their normal counterparts in that eac
  • 消息中间件如何保证消息不丢失

    一 消息队列MQ的三个阶段 1 生产者发送消息到MQ 2 MQ存储消息到内存或者硬盘 3 消费者消费消息 由于网络的原因 服务器的原因 程序的原因等等 xff0c 在每个阶段都有可能引起消息的丢失 xff1a 1 生产者发送消息到MQ xf
  • 32位系统为什么最大只支持4GB运存?

    首先要明白 1B 61 2 3b 1KB 61 2 10B 1MB 61 2 20B 1GB 61 2 30B 4GB 61 2 2 2 30B 61 2 32B b表示一个比特位 xff0c B表示一个字节 xff0c 一字节等于8个比特
  • 数据库和Spring事务隔离级别

    事务隔离级别 xff0c 指的是数据库多个并发事务操作共享数据时 xff0c 共享的数据对多个并发事务之间的可见性和影响程度 隔离的内容主要指数据方面 具体举例来说就是一个事务A读操作时 xff0c 其他并发事务修改操作事务A读的数据时 x
  • Java中各种锁的详细介绍(二):悲观锁和乐观锁

    Java中锁的类型多种多样 xff0c 有简单有复杂 xff0c 适合各种不同的应用场景 xff0c 接下来会分几章给大家详细介绍java中各种类型的锁 一 悲观锁和乐观锁的说明 1 悲观锁 Pessimistic Lock xff1a 对
  • Java中各种锁的详细介绍(三):自旋锁 VS 适应性自旋锁

    一 自旋锁 在介绍自旋锁前 xff0c 需要介绍一些前提知识来帮助大家更好的明白自旋锁的概念 阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成 xff0c 这种状态转换需要耗费处理器时间 如果同步代码块中的内容过于简单 xff0c
  • Java中各种锁的详细介绍(四):无锁|偏向锁|轻量级锁|重量级锁

    无锁 偏向锁 轻量级锁和重量级锁 xff0c 都是指锁的状态 xff0c 专门针对synchronized的 一 Synchronized如何实现线程同步 xff1f Java对象中有两个重要概念 xff1a Java对象头 和 Monit
  • A-star 算法原理分析

    搜索算法 图论中 xff0c 应用最广泛的就是搜索算法了 xff0c 比如 xff0c 深度优先搜索 广度优先搜索等 在介绍 Dijkstra 算法那篇中 xff0c 除了深度优先 广度优先这种暴力搜索算法 xff0c 还有一些最短路算法也