JavaScript 实现 A* 寻路算法详解与代码示例

作者:函数茶馆时间:2026-10-22 19:39:49浏览:518

本文详细解析 A* 寻路算法在 JavaScript 中的实现过程。通过开启列表与关闭列表的管理,结合 F=G+H 估价函数计算最优路径,并处理障碍物与斜向移动逻辑。文章提供完整的 JS 代码示例,适用于游戏地图寻路等场景,帮助开发者掌握核心算法原理。

JavaScript 实现 A* 寻路算法详解与代码示例

本文通过具体实例,介绍如何使用 JavaScript 实现 A* 寻路算法,供读者参考学习。

近期在处理百度前端技术学院的相关题目时,遇到了寻路功能的需求,因此查阅了相关技术博客进行深入研究。

阅读了 Create Chen 撰写的《理解 A* 寻路算法具体过程》一文后,我对 A* 算法的核心原理有了清晰的认识。该作者的讲解通俗易懂,配图精良,可见投入了大量心血。若由我来撰写,恐怕难以达到如此水准。

然而,原文提供的源码并非 JavaScript 版本,导致我无法直接复用。鉴于此,我决定重新撰写一篇面向 JavaScript 学习者的教程。前文的概念描述我将沿用原作者的表述,因为我的文字表达能力尚显不足,借用原文能更准确地传达知识。

简易地图构建

简易地图构建

JavaScript 实现 A* 寻路算法详解与代码示例

如图所示的简易地图中,绿色方块代表起点(标记为 A),中间蓝色区域为障碍物,红色方块代表终点(标记为 B)。为了便于使用二维数组表示地图结构,我们将整个地图划分为若干个小方块网格。

二维数组在游戏开发中应用广泛,例如贪吃蛇和俄罗斯方块的基本原理均涉及方块的移动。而在大型游戏中,地图则是通过在这些小方块上铺设不同的“地貌”纹理来构建的。

寻路执行步骤

寻路执行步骤

1. 从起点 A 开始,将其作为待处理节点存入“开启列表”(Open List),该列表用于存放待检查的方格。 2. 查找起点 A 周围所有可通行的方格,将它们加入“开启列表”,并将它们的“父方格”设置为 A。 3. 将起点 A 从“开启列表”中移除,并加入“关闭列表”(Closed List)。关闭列表用于存储已检查且无需再次处理的方格。

JavaScript 实现 A* 寻路算法详解与代码示例

图中带有浅绿色边框的方块表示已加入“开启列表”等待检查;带有淡蓝色边框的起点 A 表示已移入“关闭列表”,无需再执行检查操作。

接下来,从“开启列表”中筛选出相对最优的方格。判断标准是通过公式 F = G + H 计算得出的数值。

F = G + H 其中,G 表示从起点 A 移动到网格上指定方格的实际移动耗费(允许沿斜方向移动)。 H 表示从指定方格移动到终点 B 的预计耗费(此处设定仅允许上下左右移动,不计算斜向)。

JavaScript 实现 A* 寻路算法详解与代码示例

假设横向或纵向移动一个格子的耗费为 10,为了便于计算,沿斜方向移动一个格子的耗费设为 14。为了直观展示 F、G、H 的运算过程,图中方块左上角的数字代表 F 值,左下角代表 G 值,右下角代表 H 值。请核对计算结果是否与您的预期一致?

从“开启列表”中选取 F 值最低的方格 C(即绿色起始方块 A 右侧的方块),并对其进行以下处理:

4. 将该方格从“开启列表”中移除,并加入“关闭列表”。 5. 检查该方格周围所有相邻且可到达的方格(忽略障碍物和已在“关闭列表”中的方格)。若这些方格尚未在“开启列表”中,则将其加入,计算其 G、H 和 F 值,并设置其“父方格”为当前选中的方格 C。 6. 若某个相邻方格 D 已在“开启列表”中,则检查经由当前方格 C 到达 D 的路径是否更优(即 G 值是否更低)。若新路径的 G 值更低,则将 D 的“父方格”更新为 C,并重新计算 D 的 F 值和 G 值(H 值不变,因为它是基于目标点计算的固定值)。若新路径 G 值较高,说明经由 C 到达 D 并非明智选择,此时不做任何操作。

JavaScript 实现 A* 寻路算法详解与代码示例

如图所示,我们选中了 F 值最小的方格 C,将其从“开启列表”移除并加入“关闭列表”。C 的右侧及上下方均为墙壁,故不予考虑;左侧为起始方块,已存在于“关闭列表”中,亦不考虑。因此,C 周围的候选方格仅剩 4 个。以 C 下方的格子为例,其当前 G 值为 14,若经由 C 到达该格子,G 值将变为 10 + 10 = 20,高于现有的 14,因此不做任何更改。

随后,我们继续从“开启列表”中查找 F 值最小的方格。此时发现 C 上方和下方的方格 F 值同为 54。在这种情况下,任选其一即可,例如我们选择 C 下方的方格 D。

JavaScript 实现 A* 寻路算法详解与代码示例

D 的右侧及右上方均为墙壁,故不考虑。为何右下角的方格未被加入“开启列表”?这是因为若 C 下方的方格不可通行,要到达 C 右下角的方格需从“方块的角”穿过。程序中通常设置是否允许这种走法(本示例中不允许斜穿障碍物角)。

JavaScript 实现 A* 寻路算法详解与代码示例

重复上述过程:从“开启列表”中找出 F 值最小的方格,将其移出并加入“关闭列表”,再查找其周围可到达的方格,如此循环迭代。

寻路何时停止?当“开启列表”中出现目标终点方块时,说明路径已成功找到。

路径回溯方法

路径回溯方法

JavaScript 实现 A* 寻路算法详解与代码示例

如上图所示, 除了起始方块, 每一个曾经或者现在还在 "开启列表" 里的方块, 它都有一个 "父方块", 通过 "父方块" 可以索引到最初的 "起始方块", 这就是路径.

算法流程抽象

算法流程抽象

将起始格添加到“开启列表”。

do { 寻找开启列表中 F 值最低的格子,称其为当前格。 将其切换到关闭列表。 对当前格相邻的 8 个方向中的每一个进行判断: if (它不可通过 || 已经在 "关闭列表" 中) { 什么也不做。 } if (它不在开启列表中) { 把它添加进 "开启列表", 把当前格作为这一格的父节点, 计算这一格的 FGH。 } if (它已经在开启列表中) { if (用G值为参考检查新的路径是否更好, 更低的G值意味着更好的路径) { 把这一格的父节点改成当前格, 并且重新计算这一格的 GF 值。 } } } while( 目标格已经在 "开启列表", 这时候路径被找到 )

若开启列表已空仍未找到目标,则说明不存在可行路径。

最后,从目标格开始,沿着每一格的父节点移动,直到回到起始格,即为所求路径。

JavaScript 代码实现如下

JavaScript 代码实现如下:


//其中的MAP.arr是二维数组
function searchRoad(start_x,start_y,end_x,end_y){
  var openList=[],  //开启列表
    closeList=[],  //关闭列表
    result=[],   //结果数组
    result_index;  //结果数组在开启列表中的序号
  openList.push({x:start_x,y:start_y,G:0});//把当前点加入到开启列表中,并且G是0
  do{
    var currentPoint = openList.pop();
    closeList.push(currentPoint);
    var surroundPoint=SurroundPoint(currentPoint);
    for(var i in surroundPoint) {
      var item = surroundPoint[i];        //获得周围的八个点
      if (
        item.x>=0 &&              //判断是否在地图上
        item.y>=0 &&
        item.x<MAP.rows &&
        item.y<MAP.cols &&
        MAP.arr[item.x][item.y] != 1 &&     //判断是否是障碍物
        !existList(item, closeList) &&     //判断是否在关闭列表中
        MAP.arr[item.x][currentPoint.y]!=1 &&  //判断之间是否有障碍物,如果有障碍物是过不去的
        MAP.arr[currentPoint.x][item.y]!=1) {
        //g 到父节点的位置
        //如果是上下左右位置的则g等于10,斜对角的就是14
        var g = currentPoint.G + ((currentPoint.x - item.x) * (currentPoint.y - item.y) == 0 ? 10 : 14);
        if (!existList(item, openList)) {    //如果不在开启列表中
          //计算H,通过水平和垂直距离进行确定
          item['H'] = Math.abs(end_x - item.x) * 10 + Math.abs(end_y - item.y) * 10;
          item['G'] = g;
          item['F'] = item.H + item.G;
          item['parent'] = currentPoint;
          openList.push(item);
        }
        else {                 //存在在开启列表中,比较目前的g值和之前的g的大小
          var index = existList(item, openList);
          //如果当前点的g更小
          if (g < openList[index].G) {
            openList[index].parent = currentPoint;
            openList[index].G = g;
            openList[index].F=g+openList[index].H;
          }
        }
      }
    }
    //如果开启列表空了,没有通路,结果为空
    if(openList.length==0) {
      break;
    }
    openList.sort(sortF);//这一步是为了循环回去的时候,找出 F 值最小的, 将它从 "开启列表" 中移掉
  }while(!(result_index=existList({x:end_x,y:end_y},openList)));
  //判断结果列表是否为空
  if(!result_index) {
    result=[];
  }
  else {
    var currentObj=openList[result_index];
    do{
      //把路劲节点添加到result当中
      result.unshift({
        x:currentObj.x,
        y:currentObj.y
      });
      currentObj=currentObj.parent;
    }while (currentObj.x!=start_x || currentObj.y!=start_y);
  }
  return result;
}
//用F值对数组排序
function sortF(a,b){
  return b.F- a.F;
}
//获取周围八个点的值
function SurroundPoint(curPoint){
  var x=curPoint.x,y=curPoint.y;
  return [
    {x:x-1,y:y-1},
    {x:x,y:y-1},
    {x:x+1,y:y-1},
    {x:x+1,y:y},
    {x:x+1,y:y+1},
    {x:x,y:y+1},
    {x:x-1,y:y+1},
    {x:x-1,y:y}
  ]
}
//判断点是否存在在列表中,是的话返回的是序列号
function existList(point,list) {
  for(var i in list) {
    if(point.x==list[i].x && point.y==list[i].y) {
      return i;
    }
  }
  return false;
}

更多关于 JavaScript 相关内容感兴趣的读者可查看本站专题:《JavaScript数学运算用法总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript数组操作技巧总结》、《JavaScript排序算法总结》、《JavaScript遍历算法与技巧总结》、《JavaScript查找算法技巧总结》及《JavaScript错误与调试技巧总结》。

希望本文所述的 JavaScript 程序设计内容能对大家有所帮助。

作者最新文章