A*算法(A-Star Algorithm)详解

A*算法(A-Star Algorithm)详解

A*算法(A-Star Algorithm)是一种启发式搜索算法,广泛应用于路径规划(如游戏寻路、机器人导航)、图遍历等领域。它通过结合实际代价启发式估计代价,在保证找到最优路径的同时,显著减少搜索范围,效率远高于传统的Dijkstra算法或贪心搜索。

在这里插入图片描述

一、核心思想:评估函数f(n)

A*算法的核心是定义一个评估函数,用于优先扩展“最有希望”到达目标的节点。该函数表示为:
f(n)=g(n)+h(n) f(n) = g(n) + h(n) f(n)=g(n)+h(n)

  • g(n):从起点(Start)到当前节点 nnn 的实际代价(已消耗的代价)。例如,在网格地图中,若每一步移动代价为1,则 g(n)g(n)g(n) 是起点到 nnn 的步数。
  • h(n):从当前节点 nnn 到目标节点(Goal)的估计代价(启发式预测)。它是算法的“智能”来源,需满足可采纳性(Admissible)——即不高估实际代价(否则可能错过最优解)。
  • f(n):节点 nnn 的总代价估计,指导搜索方向(优先扩展 f(n)f(n)f(n) 最小的节点)。
在这里插入图片描述

二、算法步骤

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

1. 初始化数据结构
  • 开放列表(Open List):优先队列(最小堆),存储待扩展的节点,按 f(n)f(n)f(n) 排序(fff 最小的节点在顶部)。初始时仅包含起点。
  • 关闭列表(Closed List):集合或哈希表,存储已扩展过的节点(避免重复处理)。初始为空。
  • 代价记录:维护两个字典(或数组),分别记录每个节点的 g(n)g(n)g(n)(实际代价)和 f(n)f(n)f(n)(总代价)。
2. 主循环:扩展节点

重复以下步骤直到找到目标或开放列表为空(无路径):

  • 步骤1:从开放列表中取出 f(n)f(n)f(n) 最小的节点 nnn(称为当前节点)。
  • 步骤2:若 nnn 是目标节点,回溯路径并返回结果(算法结束)。
  • 步骤3:将 nnn 加入关闭列表(标记为已处理)。
  • 步骤4:遍历 nnn 的所有邻居节点 mmm(上下左右、对角线等,取决于移动规则):
    • 若 mmm 在关闭列表中:跳过(已处理过更优路径)。
    • 计算临时 ggg 值:temp_g=g(n)+从n到m的移动代价temp\_g = g(n) + \text{从}n\text{到}m\text{的移动代价}temp_g=g(n)+从n到m的移动代价(如相邻节点代价为1,对角线为√2)。
    • 若 mmm 不在开放列表中,或 temp_g<g(m)temp\_g < g(m)temp_g<g(m)(发现更优路径):
      • 更新 g(m)=temp_gg(m) = temp\_gg(m)=temp_g。
      • 计算 h(m)h(m)h(m)(启发式估计值)。
      • 计算 f(m)=g(m)+h(m)f(m) = g(m) + h(m)f(m)=g(m)+h(m)。

若 mmm 不在开放列表中,将其加入开放列表;否则,更新其在开放列表中的优先级(因 f(m)f(m)f(m) 可能变小)。

在这里插入图片描述
3. 路径回溯

当目标节点被取出开放列表时,从目标节点反向回溯父节点(扩展时记录每个节点的父节点),直到回到起点,得到完整路径。

三、关键:启发函数h(n)的选择

h(n)h(n)h(n) 的选择直接影响算法效率和最优性。需满足可采纳性(不高估实际代价),常见类型如下:

1. 曼哈顿距离(Manhattan Distance)

适用于四方向移动(上下左右)的网格地图:
h(n)=∣xn−xgoal∣+∣yn−ygoal∣ h(n) = |x_n - x_{goal}| + |y_n - y_{goal}| h(n)=∣xn​−xgoal​∣+∣yn​−ygoal​∣
其中 (xn,yn)(x_n, y_n)(xn​,yn​) 是当前节点坐标,(xgoal,ygoal)(x_{goal}, y_{goal})(xgoal​,ygoal​) 是目标坐标。

2. 欧几里得距离(Euclidean Distance)

适用于八方向移动(允许对角线)的场景:
h(n)=(xn−xgoal)2+(yn−ygoal)2 h(n) = \sqrt{(x_n - x_{goal})^2 + (y_n - y_{goal})^2} h(n)=(xn​−xgoal​)2+(yn​−ygoal​)2​

3. 切比雪夫距离(Chebyshev Distance)

适用于八方向移动且允许斜向一步到位的情况(如国际象棋中的王):
h(n)=max⁡(∣xn−xgoal∣,∣yn−ygoal∣) h(n) = \max(|x_n - x_{goal}|, |y_n - y_{goal}|) h(n)=max(∣xn​−xgoal​∣,∣yn​−ygoal​∣)

4. 其他变种

根据具体问题设计专用启发函数(如路径规划中的预计算代价图)。

四、特性分析

  • 最优性:若 h(n)h(n)h(n) 可采纳(不高估),A* 一定能找到最短路径(前提是所有边的代价非负)。
  • 完备性:若存在路径且图有限,A* 最终会找到路径;若不存在路径,开放列表会为空。
  • 效率:启发函数 h(n)h(n)h(n) 越接近实际代价(但不超过),开放列表扩展的节点越少,效率越高。例如,当 h(n)h(n)h(n) 等于实际代价(仅目标节点的 h=0h=0h=0),A* 退化为Dijkstra算法(全局搜索);当 h(n)h(n)h(n) 强启发(如目标明确时的直线距离),搜索范围大幅缩小。
在这里插入图片描述

五、应用场景

  • 游戏开发:NPC角色寻路(如《魔兽世界》《英雄联盟》)。
  • 机器人导航:自动驾驶中的局部路径规划。
  • 地图服务:GPS导航中的实时路径计算(结合实时交通数据调整代价)。

六、示例:网格地图寻路

假设一个4x4网格(起点(0,0),终点(3,3)),四方向移动,每步代价为1。

  1. 初始化:开放列表包含起点(0,0),g=0g=0g=0,h=∣0−3∣+∣0−3∣=6h=|0-3|+|0-3|=6h=∣0−3∣+∣0−3∣=6,f=6f=6f=6。
  2. 第一次扩展:取出(0,0),扩展邻居(0,1)、(1,0)。计算它们的 g=1g=1g=1,hhh 分别为 ∣0−3∣+∣1−3∣=5|0-3|+|1-3|=5∣0−3∣+∣1−3∣=5(f=6f=6f=6)和 ∣1−3∣+∣0−3∣=5|1-3|+|0-3|=5∣1−3∣+∣0−3∣=5(f=6f=6f=6),加入开放列表。
  3. 后续扩展:每次选择 fff 最小的节点(初始阶段多个节点 f=6f=6f=6),逐步向终点逼近。最终当终点(3,3)被取出时,回溯路径为:(0,0)→(1,0)→(2,0)→(3,0)→(3,1)→(3,2)→(3,3)(或其他等价最短路径)。

总结

A*算法通过平衡实际代价与启发式估计,在路径规划中实现了“高效+最优”的双重优势。理解其核心评估函数、数据结构(开放/关闭列表)及启发函数的选择,是掌握该算法的关键。实际应用中,需根据场景调整启发函数,以在效率和最优性之间取得平衡。

在这里插入图片描述

用生活场景解释A*算法

要理解A算法,咱们可以用一个生活中找路的场景来打比方——就像你在陌生的小区里找朋友家,手里有一张地图(网格或道路图),需要最快找到目的地。A算法就是帮你“聪明选路”的小助手,它会一边算你已经走了多远(实际代价),一边猜剩下的路大概还要走多远(预估代价),最终带你走最快的那条路。

一、核心思路:用“总代价”决定先走哪条路

假设你现在在起点(比如小区门口),目标是朋友家(终点)。A*算法的关键是给每个“可能经过的点”(比如路上的每个路口、格子)算一个总代价分数,分数越低,说明这条路“越值得先走”。这个总分数由两部分组成:

  • g(n):你已经走了多远(实际代价)。比如从起点到当前点,你走了300米(或3步),g(n)=300。
  • h(n):你猜还要走多远(预估代价)。比如当前点离终点看起来还有200米(或2步),h(n)=200(但不能乱猜,得“靠谱”)。

总分数 f(n) = g(n) + h(n),相当于“从起点到终点经过这里的总步数/总距离”。A*算法会优先走f(n)最小的点——就像你会优先选“已经走了100米+还剩100米=总共200米”的路,而不是“已经走了200米+还剩300米=总共500米”的路。

二、具体怎么操作?像“整理待选清单”一样找路

A*算法的过程可以想象成你拿着一张“待选清单”(开放列表)和一张“已检查清单”(关闭列表),一步步缩小范围:

1. 开始:把起点放进待选清单

一开始,你只知道起点,所以待选清单里只有起点,它的g=0(还没走),h是你猜的起点到终点的距离(比如直线距离),f=g+h。

2. 循环:每次从待选清单里挑“最划算”的点

每次从待选清单里找出f(n)最小的点(也就是“最值得走”的点),把它从待选清单移到已检查清单(表示你已经认真考虑过这个点了)。

3. 检查是否到终点

如果这个点刚好是终点,恭喜!你已经找到路了,接下来只需要“倒推”回去,就能得到完整路径(比如:终点←上一步←再上一步←…←起点)。

4. 扩展邻居:看看周围有哪些路可选

如果还没到终点,你需要看看当前点的“邻居”(比如上下左右四个方向的路口,或者对角线的路口,取决于你能怎么走)。对每个邻居:

  • 如果邻居已经在已检查清单(你之前已经仔细看过这个点,而且当时的路线更优),跳过它(不用再浪费时间)。
  • 如果邻居不在待选清单,或者你找到了一条去邻居的“更短实际路径”(比如之前算过去邻居要走500米,现在发现走另一条路只要400米),就更新它的g(n)(实际走了多远),重新算f(n)=g(n)+h(n),然后把它放进待选清单。

三、关键:“猜得靠谱”才能找得快

A*算法好不好用,关键看你怎么“猜”h(n)(剩下的路有多远)。这个“猜测”必须满足一个条件:不能高估实际距离(比如实际要走200米,你不能猜300米)。否则,算法可能会漏掉真正的最短路径。

举个例子:

  • 如果你在走直角的网格路(比如只能上下左右走,不能斜着走),最靠谱的h(n)是“曼哈顿距离”——横竖两边的步数加起来。比如当前点在(2,3),终点在(5,7),那么横向需要走5-2=3步,纵向需要走7-3=4步,h(n)=3+4=7(实际可能需要更多步,但绝不会更少)。
  • 如果你可以斜着走(比如八方向移动),h(n)可以用“欧几里得距离”——直接算两点之间的直线距离(比如√[(5-2)²+(7-3)²]≈5),这比曼哈顿距离更接近实际步数。

四、为什么A*比“笨办法”好?

传统方法比如Dijkstra算法,相当于“盲目”地从起点开始,把所有可能的路都展开,直到找到终点(像撒网捕鱼)。而A*算法因为有h(n)的“聪明猜测”,会优先展开那些“看起来离终点更近”的点(像瞄准目标撒网),所以找路更快,计算量更小

总结:A*算法的本质

A*算法就像一个“聪明的导航员”:

  • 它知道你已经走了多远(g(n)),也知道大概还要走多远(h(n)),从而选出“总代价最小”的路。
  • 它不会乱猜(h(n)不吹牛),所以最终一定能找到最短路径(如果存在的话)。
  • 生活中,它能在游戏里帮角色快速找路、在导航软件里帮你避开拥堵(调整h(n)为实时路况),非常实用!

Read more

【Java Web学习 | 第14篇】JavaScript(8) -正则表达式

【Java Web学习 | 第14篇】JavaScript(8) -正则表达式

🌈个人主页: Hygge_Code🔥热门专栏:从0开始学习Java | Linux学习| 计算机网络💫个人格言: “既然选择了远方,便不顾风雨兼程” 文章目录 * JavaScript 正则表达式详解 * 什么是正则表达式🤔 * JavaScript 正则表达式的定义与使用🥝 * 1. 字面量语法 * 2. 常用匹配方法 * test() 方法🍋‍🟩 * exec() 方法🍋‍🟩 * 正则表达式的核心组成部分🐦‍🔥 * 1. 元字符 * 边界符 * 量词 * 字符类 * 2. 修饰符 * 简单示例🍂 JavaScript 正则表达式详解 正则表达式是处理字符串的强大工具,在 JavaScript 中被广泛应用于表单验证、文本处理和数据提取等场景。本文将从正则表达式的基本概念出发,详细介绍其语法规则和实际应用方法。 什么是正则表达式🤔 正则表达式是用于匹配字符串中字符组合的模式,在 JavaScript

By Ne0inhk
《算法题讲解指南:递归,搜索与回溯算法--递归》--3.反转链表,4.两两交换链表中的节点,5.快速幂

《算法题讲解指南:递归,搜索与回溯算法--递归》--3.反转链表,4.两两交换链表中的节点,5.快速幂

🔥小叶-duck:个人主页 ❄️个人专栏:《Data-Structure-Learning》 《C++入门到进阶&自我学习过程记录》 《算法题讲解指南》--优选算法 《算法题讲解指南》--递归、搜索与回溯算法 ✨未择之路,不须回头 已择之路,纵是荆棘遍野,亦作花海遨游 目录 3.反转链表 题目链接: 题目描述: 题目示例: 解法(递归): 算法思路: C++算法代码: 算法总结及流程解析: 4.两两交换链表中的节点 题目链接: 题目描述: 题目示例: 解法(递归): 算法思路: C++算法代码: 算法总结及流程解析: 5.快速幂 题目链接: 题目描述: 题目示例: 解法(递归-快速幂): 算法思路: C+

By Ne0inhk
前端异常捕获与统一格式化:从 console.log(error) 到服务端上报

前端异常捕获与统一格式化:从 console.log(error) 到服务端上报

🧑 博主简介:ZEEKLOG博客专家,「历代文学网」(公益文学网,PC端可以访问:https://lidaiwenxue.com/#/?__c=1000,移动端可关注公众号 “ 心海云图 ” 微信小程序搜索“历代文学”)总架构师,首席架构师,也是联合创始人!16年工作经验,精通Java编程,高并发设计,分布式系统架构设计,Springboot和微服务,熟悉Linux,ESXI虚拟化以及云原生Docker和K8s,热衷于探索科技的边界,并将理论知识转化为实际应用。保持对新技术的好奇心,乐于分享所学,希望通过我的实践经历和见解,启发他人的创新思维。在这里,我希望能与志同道合的朋友交流探讨,共同进步,一起在技术的世界里不断学习成长。 🤝商务合作:请搜索或扫码关注微信公众号 “ 心海云图 ” 前端异常捕获与统一格式化:从 console.log(error) 到服务端上报 引言 在前端开发中,异常监控是保证应用稳定性的重要一环。当用户遇到页面白屏、功能不可用等问题时,如果能及时收集到详细的错误信息(包括堆栈、

By Ne0inhk
合并两个升序链表 与 合并k个升序链表

合并两个升序链表 与 合并k个升序链表

玩转链表合并✨:从 2 个到 k 个升序链表的通关秘籍 在算法面试的高频题库里,链表相关题目一直是 “常客”,而 “升序链表合并” 更是其中的经典题型 —— 从基础的「合并两个升序链表」,到进阶的「合并 k 个升序链表」,难度层层递进,却也藏着通用的解题逻辑。今天就带大家拆解这两道题,从核心思路到代码实现,再到面试官常问的 “灵魂拷问”,一次性吃透✨! leetcode链接: 合并两个有序链表  合并 K 个升序链表 一、合并两个升序链表:基础版 “链表拼接术” 📌 给定两个升序排列的单链表,要求合并成一个新的升序链表并返回头节点。这道题是链表合并的 “入门款”,掌握它的核心逻辑,才能轻松应对后续的 k 个链表合并。 1. 解题关键:3 个 “核心武器” 想要优雅解决这道题,离不开三个关键设计,少一个都可能让边界处理变得繁琐:

By Ne0inhk