A*算法详解

A*算法详解

    • 一、A*算法基础概念
      • 1.1 算法定位
      • 1.2 核心评估函数
      • 1.3 关键数据结构
    • 二、A*算法的核心步骤
    • 三、启发函数设计
      • 3.1 网格地图中的启发函数
      • 3.2 启发函数的选择原则
    • 三、Java代码实现
    • 四、启发函数的设计与优化
      • 4.1 启发函数的可采纳性
      • 4.2 启发函数的效率影响
      • 4.3 常见启发函数对比
    • 五、A*算法的应用场景与拓展
      • 5.1 典型应用
      • 5.2 算法拓展
    • 六、A*算法的优缺点
      • 优点
      • 缺点

从游戏中的角色寻路到机器人导航,从地图软件的路线规划到无人机路径优化,A算法都发挥着核心作用,本文我将深入解析A算法的核心原理、实现步骤、启发函数设计及实际应用,并结合Java代码示例,带你全面掌握这一路径搜索算法。

一、A*算法基础概念

1.1 算法定位

A*算法是一种启发式搜索算法,它结合了Dijkstra算法的完备性(保证找到解)和贪婪最佳优先搜索的高效性(基于启发信息快速导向目标),通过引入评估函数引导搜索方向,能在复杂网格或图中快速找到从起点到目标的最优路径。

1.2 核心评估函数

A*算法的核心是评估函数f(n),用于判断节点n的"优先级":

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

  • g(n):从起点到当前节点n实际代价(已走过的路径长度)。
  • h(n):从当前节点n到目标节点的估计代价(启发函数),这是A*算法的"智能"所在。

1.3 关键数据结构

  • 开放列表(Open List):存储待探索的节点,按f(n)值升序排列(通常用优先队列实现)。
  • 关闭列表(Closed List):存储已探索的节点,避免重复访问(通常用哈希表或集合实现)。
  • 节点(Node):包含坐标、g(n)h(n)f(n)及父节点指针(用于回溯路径)。

二、A*算法的核心步骤

A*算法的执行流程可概括为以下步骤:

  1. 初始化

    • 将起点加入开放列表,计算其g(n)=0h(n)(根据启发函数),f(n)=g(n)+h(n)
    • 关闭列表初始为空。
  2. 循环搜索

    • 从开放列表中取出f(n)最小的节点current,移至关闭列表。
    • current是目标节点,回溯路径并结束。
    • 遍历current的所有相邻节点neighbor
      • neighbor在关闭列表中,跳过。
      • 计算neighborg(n)current.g + 相邻节点距离)。
      • neighbor不在开放列表,或新g(n)更小:
        • 更新neighborg(n)h(n)f(n),设置父节点为current
        • 若不在开放列表,加入开放列表。
  3. 路径回溯

    • 从目标节点开始,通过父节点指针反向追溯至起点,得到最优路径。

三、启发函数设计

启发函数h(n)的设计直接影响A*算法的效率和最优性,需满足可采纳性h(n) ≤ 实际代价),以保证找到最优解。常见的启发函数如下:

3.1 网格地图中的启发函数

  • 曼哈顿距离(Manhattan Distance):适用于只能上下左右移动的网格(如方格地图):

h(n) = |xₙ - xₜ| + |yₙ - yₜ|

(xₙ,yₙ)为当前节点坐标,(xₜ,yₜ)为目标节点坐标)

  • 欧几里得距离(Euclidean Distance):适用于允许斜向移动的网格:

h(n) = √[(xₙ - xₜ)² + (yₙ - yₜ)²]

  • 切比雪夫距离(Chebyshev Distance):适用于允许8方向移动(包括对角线)的网格:

h(n) = max(|xₙ - xₜ|, |yₙ - yₜ|)

3.2 启发函数的选择原则

  • 最优性优先:选择可采纳的启发函数(如曼哈顿距离),确保找到最短路径。
  • 效率优先:在不要求严格最优时,可使用略高估的启发函数加速搜索(如加权曼哈顿距离)。

三、Java代码实现

以下是基于网格地图的A*算法实现,使用曼哈顿距离作为启发函数,支持4方向移动:

import java.util.*;// 节点类
class Node implements Comparable<Node> {int x, y; // 坐标int g; // 起点到当前节点的实际代价int h; // 到目标的估计代价int f; // f = g + hNode parent; // 父节点public Node(int x, int y) {this.x = x;this.y = y;}// 按f值升序排序,f相同则按h值@Overridepublic int compareTo(Node other) {if (this.f != other.f) {return Integer.compare(this.f, other.f);}return Integer.compare(this.h, other.h);}// 重写equals和hashCode,用于关闭列表去重@Overridepublic 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;}@Overridepublic int hashCode() {return Objects.hash(x, y);}
}public class AStarAlgorithm {// 网格地图:0为可通行,1为障碍物private int[][] grid;// 方向数组:上下左右private int[][] directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};public AStarAlgorithm(int[][] grid) {this.grid = grid;}// 启发函数:曼哈顿距离private int calculateH(Node node, Node target) {return Math.abs(node.x - target.x) + Math.abs(node.y - target.y);}// 搜索路径public List<Node> findPath(Node start, Node target) {PriorityQueue<Node> openList = new PriorityQueue<>();Set<Node> closedList = new HashSet<>();// 初始化起点start.g = 0;start.h = calculateH(start, target);start.f = start.g + start.h;openList.add(start);while (!openList.isEmpty()) {Node current = openList.poll();// 找到目标,回溯路径if (current.equals(target)) {return reconstructPath(current);}closedList.add(current);// 遍历相邻节点for (int[] dir : directions) {int nx = current.x + dir[0];int ny = current.y + dir[1];// 检查是否越界或为障碍物if (nx < 0 || nx >= grid.length || ny < 0 || ny >= grid[0].length || grid[nx][ny] == 1) {continue;}Node neighbor = new Node(nx, ny);if (closedList.contains(neighbor)) {continue;}// 计算相邻节点的g值(假设相邻节点距离为1)int newG = current.g + 1;// 若邻居不在开放列表,或新g值更小if (!openList.contains(neighbor) || newG < neighbor.g) {neighbor.g = newG;neighbor.h = calculateH(neighbor, target);neighbor.f = neighbor.g + neighbor.h;neighbor.parent = current;if (!openList.contains(neighbor)) {openList.add(neighbor);}}}}// 未找到路径return null;}// 回溯路径(从目标到起点)private List<Node> reconstructPath(Node target) {List<Node> path = new ArrayList<>();Node current = target;while (current != null) {path.add(current);current = current.parent;}Collections.reverse(path); // 反转路径,从起点到目标return path;}// 测试public static void main(String[] args) {// 5x5网格,1为障碍物int[][] grid = {{0, 0, 0, 0, 0},{0, 1, 1, 1, 0},{0, 0, 0, 1, 0},{0, 1, 0, 1, 0},{0, 0, 0, 0, 0}};AStarAlgorithm aStar = new AStarAlgorithm(grid);Node start = new Node(0, 0);Node target = new Node(4, 4);List<Node> path = aStar.findPath(start, target);if (path != null) {System.out.println("找到路径:");for (Node node : path) {System.out.println("(" + node.x + "," + node.y + ")");}} else {System.out.println("未找到路径");}}
}

四、启发函数的设计与优化

4.1 启发函数的可采纳性

启发函数h(n)必须满足不高估实际代价h(n) ≤ 实际从n到目标的代价),才能保证A*算法找到最优路径。例如:

  • 网格中,曼哈顿距离对于4方向移动是可采纳的,欧几里得距离对于任意方向移动是可采纳的。
  • h(n)=0,A*算法退化为Dijkstra算法,虽能找到最优路径但效率低。

4.2 启发函数的效率影响

  • h(n)越接近实际代价,A*算法搜索的节点越少,效率越高。
  • h(n)完全等于实际代价,A*算法会直接沿最优路径搜索,效率最高。
  • h(n)高估实际代价(不可采纳),算法可能找不到最优路径,但速度更快(适用于对效率要求高、可接受近似解的场景)。

4.3 常见启发函数对比

启发函数适用场景可采纳性效率
曼哈顿距离4方向网格移动
欧几里得距离任意方向移动(如无人机)
切比雪夫距离8方向网格移动
加权曼哈顿距离允许近似解的场景极高

五、A*算法的应用场景与拓展

5.1 典型应用

  • 游戏开发:角色寻路(如RPG游戏中NPC的移动路径)。
  • 机器人导航:自主移动机器人在室内或室外环境中的路径规划。
  • 地图软件:驾车/步行路线规划(结合道路权重、交通状况)。
  • 路径规划:无人机、无人车的避障路径计算。

5.2 算法拓展

  • 双向A*算法:同时从起点和目标开始搜索,相遇时终止,减少搜索范围。
  • 分层A*算法:在大规模地图中,先规划高层粗略路径,再细化低层路径。
  • 动态A算法(D:适用于环境动态变化的场景(如突然出现障碍物),能快速重新规划路径。

六、A*算法的优缺点

优点

  • 高效性:通过启发函数引导搜索,比Dijkstra算法搜索更少节点。
  • 最优性:在启发函数可采纳时,能找到最优路径。
  • 灵活性:可通过调整启发函数适应不同场景(精度与效率的权衡)。

缺点

  • 依赖启发函数:启发函数设计不当会导致效率低下或找不到最优路径。
  • 内存消耗:开放列表和关闭列表需存储大量节点,不适用于超大地图。
  • 静态环境:默认适用于静态环境,动态环境需结合D*等变体算法。

总结
A*算法作为一种高效的路径搜索算法,通过评估函数f(n)=g(n)+h(n)平衡了搜索的完备性和效率,在游戏开发、机器人导航等领域有着广泛应用,掌握A※算法的核心在于理解启发函数的设计原则——可采纳性与效率的权衡,以及节点的扩展和路径回溯机制。

That’s all, thanks for reading~~
觉得有用就点个赞、收进收藏夹吧!关注我,获取更多干货~

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

.net winfrom 获取上传的Excel文件 单元格的背景色

需求&#xff1a;根据Excel某行标注了黄色高亮颜色&#xff0c;说明该行数据已被用户选中(Excel文件中并没有“已选中”这一列&#xff0c;纯粹用颜色表示)&#xff0c;导入数据到数据库时标注此行已选中直接上代码&#xff1a;//选择Excel文件private void btnBrowse_Click(ob…

OpenAI GPT-4o技术详解:全能多模态模型的架构革新与生态影响

本文由「大千AI助手」原创发布&#xff0c;专注用真话讲AI&#xff0c;回归技术本质。拒绝神话或妖魔化。搜索「大千AI助手」关注我&#xff0c;一起撕掉过度包装&#xff0c;学习真实的AI技术&#xff01; ⚙️ 一、核心定义与发布背景 官方定位 GPT-4o&#xff08;“o”代表“…

⚡ 构建真正的高性能即时通讯服务:基于 Netty 集群的架构设计与实现

引子 在前面的文章中&#xff0c;我们基于 Netty 构建了一套单体架构的即时通讯服务。虽然单体架构在开发初期简单高效&#xff0c;但随着用户量的增长和业务规模的扩大&#xff0c;其局限性逐渐显现。当面对高并发场景时&#xff0c;单体 Netty 服务很容易触及性能天花板&…

原来时间序列挖掘这么简单

先搞懂&#xff1a;啥是时间序列&#xff1f;简单说&#xff0c;时间序列就是按时间顺序记下来的数据。比如&#xff1a;你每天早上 8 点测的体重&#xff0c;连起来就是 “体重时间序列”&#xff1b;超市每天的销售额&#xff0c;连起来就是 “销售时间序列”&#xff1b;城市…

基于Python的豆瓣图书数据分析与可视化系统【自动采集、海量数据集、多维度分析、机器学习】

文章目录有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主项目介绍每文一语有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主 项目介绍 豆瓣图书数据智能分析系统是一个集数据采集、清洗、分析与可视化于一体的综合性项…

2.3 数组与字符串

学习目标&#xff1a; 理解数组和字符串的概念&#xff08;存储多个数据的“盒子”&#xff09;。掌握数组的声明、初始化和遍历方法。能用字符串处理简单文本问题&#xff08;如字符计数、回文判断&#xff09;。1 一维数组 基本概念 比喻&#xff1a; 数组就像“储物柜”&…

C# 网口demo

bool _testStatus false; private void btnOpsStart_Click(object sender, EventArgs e) {int delay Convert.ToInt32(txtdelay.Text.Trim());txtView.Clear();txtView.AppendText("******************************************开始烤机*******************************…

MATLAB 安装 ACADO 的完整步骤

✅ MATLAB 安装 ACADO 的完整步骤 &#x1f4e6; 一、准备工作 1. 下载 ACADO Toolkit 官方地址&#xff1a;https://github.com/acado/acado 2. 解压 ACADO 到你指定的路径&#xff0c;例如&#xff1a; D:\user\acado-master建议路径中 不要包含中文或空格。 &#x1f9f…

[逆向工程]160个CrackMe入门实战之Afkayas.1.Exe解析(二)

[逆向工程]160个CrackMe入门实战之Afkayas.1.Exe解析&#xff08;二&#xff09; 一、前言 在逆向工程的学习路径上&#xff0c;CrackMe程序是初学者最好的练手材料。今天我们要分析的是160个CrackMe系列的第二题——Afkayas.1.Exe。这个程序由Afkayas编写&#xff0c;难度为★…

本地电脑安装Dify|内网穿透到公网

1.安装Docker Docker: Accelerated Container Application Development 2.添加 PATH 3.安装Dify https://github.com/langgenius/dify.git 把.env.example文件名改为.env 4.更换镜像源 {"builder": {"gc": {"defaultKeepStorage": "20G…

数据结构自学Day6 栈与队列

1. 栈其实栈与队列仍然属于线性表&#xff08;有n个元素构成的集合&#xff0c;逻辑结构呈现线形&#xff09;线形表&#xff1a;顺序表&#xff0c;链表&#xff0c;栈&#xff0c;队列&#xff0c;串&#xff08;字符串&#xff09;栈&#xff08;Stack&#xff09;是一种线性…

Java 异常处理详解:从基础语法到最佳实践,打造健壮的 Java 应用

作为一名 Java 开发工程师&#xff0c;你一定遇到过运行时错误、空指针异常、文件找不到等问题。Java 提供了强大的异常处理机制&#xff0c;帮助我们优雅地捕获和处理这些错误。本文将带你全面掌握&#xff1a;Java 异常体系结构try-catch-finally 的使用throw 与 throws 的区…

Fiddler弱网测试实战指南

Fiddler是一个常用的网络抓包工具&#xff0c;它也可以用来模拟弱网环境进行测试。 在测试时需要用到弱网测试&#xff0c;也就是在信号差、网络慢的情况下进行测试。比如&#xff0c;用户在地铁、电梯、地下车库等场景经常会遇到会话中断、超时等情况&#xff0c;这种就属于弱…

解决Vue页面黑底红字遮罩层报错:Unknown promise rejection reason (webpack-internal)

vue前端页面弹出黑底红色报错遮罩层报错&#xff1a;具体报错信息&#xff1a;Uncaught runtime errors: ERROR Unknown promise rejection reasonat handleError (webpack-internal:///./node_modules/webpack-dev-server/client/overlay.js:299:58)at eval (webpack-internal…

构建 Go 可执行文件镜像 | 探索轻量级 Docker 基础镜像(我应该选择哪个 Docker 镜像?)

文章目录构建 Go 可执行文件镜像典型用途探索轻量级 Docker 基础镜像构建 Go 可执行文件镜像 golang:1.23.0-bullseye 是官方 Go 镜像的一个 “build-stage” 版,用来构建 Go 可执行文件&#xff0c;而不是把它当成最终运行镜像。 dockerhub官方&#xff1a;https://hub.dock…

链表算法之【回文链表】

目录 LeetCode-234题 LeetCode-234题 给定一个单链表的头节点head&#xff0c;判断该链表是否为回文链表&#xff0c;是返回true&#xff0c;否则返回false class Solution {/*** 这里的解题思路为&#xff1a;* (1)、找中间节点* (2)、反转链表* (3)、遍历比较节点值是否相…

Playwright Python 教程:网页自动化

1. 常用工具简介及对比主流网页自动化工具对比工具支持语言浏览器支持特点适用场景PlaywrightPython, JS, .NETChromium, Firefox, WebKit跨浏览器、速度快、API简洁自动化测试、爬虫、网页操作Selenium多语言所有主流浏览器历史悠久、社区大传统自动化测试、兼容性测试Puppete…

动态数组:ArrayList的实现原理

动态数组&#xff1a;ArrayList的实现原理 大家好&#xff01;今天我们来聊聊Java集合框架中一个非常重要的数据结构——ArrayList。就像我们日常生活中使用的伸缩收纳盒一样&#xff0c;ArrayList可以根据需要自动调整大小&#xff0c;既方便又高效。那么它是如何实现这种&quo…

MIPI DSI(五) DBI 和 DPI 格式

关于 DBI 和 DPI 这两种格式的详细协议内容&#xff0c;请参考《MIPI Alliance Standard for Display Bus Interface&#xff08;V2.0&#xff09; .pdf》和《MIPI Alliance Standard for Display Pixel Interface&#xff08;DPI- 2&#xff09; .pdf》这两份文档。首先先了解…

FRP Ubuntu 服务端 + MacOS 客户端配置

一、服务端配置 1、下载frp并解压 # 创建目录并进入 mkdir -p /opt/frp && cd /opt/frp # 下载最新版&#xff08;替换URL为GitHub发布页最新版本&#xff09; wget https://github.com/fatedier/frp/releases/download/v0.59.0/frp_0.59.0_linux_amd64.tar.gz # 解压 …