当前位置: 首页 > news >正文

JS实现高效导航——A*寻路算法+导航图简化法

一、如何实现两点间路径导航

导航实现的通用步骤,一般是:

        1、网格划分

        将地图划分为网格,即例如地图是一张图片,其像素为1000*1000,那我们将此图片划分为各个10*10的网格,从而提高寻路算法的计算量。

         2、标记障碍物

        把地图划分的网格用一个二位数组存储,若此网格中有障碍物则标记为1,若可通行则标记为0,例如[[0],[1]]。

       3、将网格简化为路径节点(此步可省)

       之前是基于网格进行路径计算,但为了减少A*算法的计算量,可以将网格地图简化为“路标形式”,这种方法通常通过提取关键节点并构建稀疏图代替密集的网格图来实现。

       4、使用A*算法寻找最短路径       

二、A*(A-Star)寻路算法

        A*寻路算法是一种用于路径规划的经典算法,广泛应用于游戏开发、机器人导航和地图路径计算等领域。它是一种基于图搜索的启发式算法,可以高效地在网格或节点图中找到从起点到终点的最短路径(或代价最低路径)。


1、算法核心:如何搜索最短路径

A*算法通过综合考虑两方面的代价来进行路径搜索:

  1. 实际代价(G):从起点到当前节点的实际路径代价。
  2. 估计代价(H):从当前节点到终点的启发式估计代价,通常采用欧几里得距离、曼哈顿距离等方法。
  3. 总代价(F)F = G + H,即从起点经过当前节点,到终点的总估计代价。

2、算法步骤

  1. 初始

    • 将起点加入“打开列表”(待处理的节点列表)。
    • “关闭列表”为空(已处理的节点列表)。
  2. 搜索
    重复以下步骤,直到找到终点或打开列表为空:

    • 从打开列表中选择 F 值最小的节点作为当前节点。
    • 如果当前节点是终点,路径找到,结束。
    • 否则,将当前节点从打开列表移至关闭列表。
    • 对当前节点的所有邻居节点:
      • 如果邻居在关闭列表中,跳过。
      • 如果邻居不可通行(障碍物),跳过。
      • 如果邻居不在打开列表中,计算其 F 值,设置父节点为当前节点,并加入打开列表。
      • 如果邻居已在打开列表中,检查是否可以通过当前节点降低 G 值;若可以,更新其 F 值和父节点。
  3. 路径回溯
    如果找到终点,通过父节点逐步回溯,得到路径。

3、启发函数的选择

启发函数是 A* 的关键。常用的启发函数有:

  1. 曼哈顿距离(适合网格地图,不能斜走时使用)
  2. 欧几里得距离(适合允许对角线走法的地图)
  3. 对角线距离(适合允许斜走但代价固定的地图)

4、JS实现A*算法

        以下是js实现A*算法的实例代码,其中启发函数选用的是曼哈顿距离(计算最快),可根据自己的需求改为其他启发函数。

function aStar(grid, start, end) {// 初始化打开列表和关闭列表let openList = [];let closedList = [];// 将起点加入打开列表openList.push(start);while (openList.length > 0) {// 找到 F 值最小的节点let current = openList.reduce((a, b) => (a.f < b.f ? a : b));// 如果当前节点是终点,回溯路径if (current.x === end.x && current.y === end.y) {let path = [];while (current) {path.push([current.x, current.y]);current = current.parent;}return path.reverse();}// 从打开列表中移除当前节点,加入关闭列表openList = openList.filter((node) => node !== current);closedList.push(current);// 遍历当前节点的邻居for (let neighbor of getNeighbors(grid, current)) {if (closedList.includes(neighbor) || !neighbor.walkable) continue;let tentativeG = current.g + 1;if (!openList.includes(neighbor) || tentativeG < neighbor.g) {neighbor.g = tentativeG;neighbor.h = heuristic(neighbor, end);neighbor.f = neighbor.g + neighbor.h;neighbor.parent = current;if (!openList.includes(neighbor)) openList.push(neighbor);}}}// 如果没有找到路径,返回空数组return [];
}// 获取邻居节点
function getNeighbors(grid, node) {const directions = [[0, -1], // 上[0, 1],  // 下[-1, 0], // 左[1, 0],  // 右];const neighbors = [];for (let [dx, dy] of directions) {let x = node.x + dx;let y = node.y + dy;if (x >= 0 && x < grid.length && y >= 0 && y < grid[0].length) {neighbors.push(grid[x][y]);}}return neighbors;
}// 启发函数(曼哈顿距离),可根据需要更换为其他函数
function heuristic(nodeA, nodeB) {return Math.abs(nodeA.x - nodeB.x) + Math.abs(nodeA.y - nodeB.y);
}

三、导航图简化法

        在路径规划问题中,将网格地图简化为“路标形式”是为了减少计算量,提高效率。这种方法通常通过提取关键节点并构建稀疏图代替密集的网格图来实现。以下是一些常见的简化方法和步骤:

1. 路标节点的选取原则

路标节点是网格地图上的关键点,用于有效表示地图的主要拓扑结构。以下是一些常见的选取原则:

  • 交叉点:所有道路的交汇点,如十字路口或转角点。
  • 障碍物的拐点:障碍物形状的关键点,确保路径规划能绕过障碍物。
  • 终点和起点:直接包括起点和终点作为路标节点。
  • 显著转弯点:路径上角度变化较大的点。

2. 网格地图到路标形式的转换方法

(1)稀疏图抽取

使用算法从网格地图中提取关键点并构建稀疏图:

  • 人工标记法:手动标记地图上的关键点,用于简单地图。
  • 自动生成法
    • 凸壳算法:提取地图边界和障碍物轮廓上的拐点。
    • 关键点检测:利用角点检测算法(如Harris角点检测)自动提取转角和交汇点。

(2)可行通路的简化

在路标节点之间构建直接的可行通路,忽略网格之间的冗余信息:

  • 连通性检测:检查两节点之间是否存在无障碍直线路径。
  • 启发式距离:以欧几里得距离或其他度量方式标记路标之间的边权重。

(3)使用稀疏图代替网格图

构建的稀疏图通常存储为节点和边的列表形式:

  • 节点:路标节点的集合。
  • 边:表示两个路标节点之间的直达路径及其权重。

3. 路标简化的优点

  • 计算效率更高:减少了需要遍历的节点数,尤其在大地图中效果显著。
  • 内存占用减少:稀疏图比密集网格占用的存储空间更小。
  • 直观性增强:路标形式便于可视化和理解。

4、JS代码

    将稀疏图作为A*算法的输入,替代网格。

//稀疏图
function simplifyToSparseGraph(grid) {const rows = grid.length;const cols = grid[0].length;// 辅助函数:判断是否为关键点function isKeyPoint(x, y) {if (grid[x][y] === 0) return false; // 障碍物不是关键点// 获取邻居点状态const neighbors = [grid[x - 1]?.[y], // 上grid[x + 1]?.[y], // 下grid[x]?.[y - 1], // 左grid[x]?.[y + 1], // 右];const walkableCount = neighbors.filter((n) => n === 1).length;// 判断是否为交叉点、拐角或终端点return walkableCount !== 2;}// 提取关键点const keyPoints = [];for (let x = 0; x < rows; x++) {for (let y = 0; y < cols; y++) {if (isKeyPoint(x, y)) {keyPoints.push({ x, y });}}}// 构建稀疏图:连通关键点const sparseGraph = {};for (let { x: x1, y: y1 } of keyPoints) {sparseGraph[`${x1},${y1}`] = [];for (let { x: x2, y: y2 } of keyPoints) {if (x1 === x2 && y1 === y2) continue; // 跳过自身if (isDirectlyConnected(grid, x1, y1, x2, y2)) {const distance = Math.abs(x1 - x2) + Math.abs(y1 - y2);sparseGraph[`${x1},${y1}`].push({ x: x2, y: y2, distance });}}}return { keyPoints, sparseGraph };
}// 判断两个点是否直接连通
function isDirectlyConnected(grid, x1, y1, x2, y2) {if (x1 !== x2 && y1 !== y2) return false; // 只考虑直线连通const [start, end] = x1 === x2 ? [y1, y2] : [x1, x2];const fixed = x1 === x2 ? x1 : y1;for (let i = Math.min(start, end) + 1; i < Math.max(start, end); i++) {const value = x1 === x2 ? grid[fixed][i] : grid[i][fixed];if (value !== 1) return false; // 遇到障碍物则不连通}return true;
}//对应修订后的A*算法
function aStarOnSparseGraph(sparseGraph, start, end) {const openList = [];const closedList = new Set();const gCosts = {};const fCosts = {};const parents = {};// 转换点为字符串键const startKey = `${start.x},${start.y}`;const endKey = `${end.x},${end.y}`;// 初始化起点gCosts[startKey] = 0;fCosts[startKey] = heuristic(start, end);openList.push({ key: startKey, fCost: fCosts[startKey] });while (openList.length > 0) {// 按 F 值排序,选取 F 值最小的节点openList.sort((a, b) => a.fCost - b.fCost);const current = openList.shift();const currentKey = current.key;// 如果当前节点是终点,回溯路径if (currentKey === endKey) {return reconstructPath(parents, startKey, endKey);}closedList.add(currentKey);// 遍历当前节点的邻居for (const neighbor of sparseGraph[currentKey]) {const neighborKey = `${neighbor.x},${neighbor.y}`;if (closedList.has(neighborKey)) continue;// 计算临时 G 值const tentativeG = gCosts[currentKey] + neighbor.distance;if (gCosts[neighborKey] === undefined || tentativeG < gCosts[neighborKey]) {// 更新 G 值和 F 值gCosts[neighborKey] = tentativeG;fCosts[neighborKey] = tentativeG + heuristic({ x: neighbor.x, y: neighbor.y }, end);parents[neighborKey] = currentKey;// 如果邻居不在 openList 中,加入 openListif (!openList.find((node) => node.key === neighborKey)) {openList.push({ key: neighborKey, fCost: fCosts[neighborKey] });}}}}// 如果找不到路径,返回空数组return [];
}// 启发函数:曼哈顿距离
function heuristic(nodeA, nodeB) {return Math.abs(nodeA.x - nodeB.x) + Math.abs(nodeA.y - nodeB.y);
}// 回溯路径
function reconstructPath(parents, startKey, endKey) {const path = [];let currentKey = endKey;while (currentKey !== startKey) {const [x, y] = currentKey.split(",").map(Number);path.push({ x, y });currentKey = parents[currentKey];}const [startX, startY] = startKey.split(",").map(Number);path.push({ x: startX, y: startY });return path.reverse();
}// 示例:网格地图
const grid = [[1, 1, 1, 0, 1],[1, 0, 1, 0, 1],[1, 1, 1, 1, 1],[0, 0, 1, 0, 1],[1, 1, 1, 0, 1],
];// 调用函数
const { keyPoints, sparseGraph } = simplifyToSparseGraph(grid);console.log("关键点:", keyPoints);
console.log("稀疏图:", sparseGraph);

相关文章:

JS实现高效导航——A*寻路算法+导航图简化法

一、如何实现两点间路径导航 导航实现的通用步骤&#xff0c;一般是&#xff1a; 1、网格划分 将地图划分为网格&#xff0c;即例如地图是一张图片&#xff0c;其像素为1000*1000&#xff0c;那我们将此图片划分为各个10*10的网格&#xff0c;从而提高寻路算法的计算量。 2、标…...

Spring Authorization Server登出说明与实践

本章内容概览 Spring Security提供的/logout登出接口做了什么与如何自定义。Spring Authorization Server提供的/connect/logout登出接口做了什么与如何自定义。Spring Authorization Server提供的/oauth2/revoke撤销token接口做了什么与如何自定义。 前言 既然系统中有登录功…...

浏览器报错 | 代理服务器可能有问题,或地址不正确

1 问题描述 Windows连网情况下&#xff0c;浏览器访问地址显示“你尚未连接&#xff0c;代理服务器可能有问题&#xff0c;或地址不正确。”出现如下画面&#xff1a; 2 解决方法 途径1 控制面板-->网络与internet-->internet选项-->Internet属性-->连接-->…...

泷羽sec:shell编程(9)不同脚本的互相调用和重定向操作

声明&#xff1a; 学习视频来自B站up主 泷羽sec 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&#…...

Milvus×OPPO:如何构建更懂你的大模型助手

01. 背景 AI业务快速增长下传统关系型数据库无法满足需求。 2024年恰逢OPPO品牌20周年&#xff0c;OPPO也宣布正式进入AI手机的时代。超千万用户开始通过例如通话摘要、新小布助手、小布照相馆等搭载在OPPO手机上的应用体验AI能力。 与传统的应用不同的是&#xff0c;在AI驱动的…...

单片机几大时钟源

在单片机中&#xff0c;MSI、HSI和HSE通常指的是用于内部晶振配置的不同功能模块&#xff1a; MSI (Master Oscillator System Interface)&#xff1a;这是最低级的一种时钟源管理单元&#xff0c;它控制着最基本的系统时钟&#xff08;SYSCLK&#xff09;&#xff0c;一般由外…...

reverse学习总结(12)

一.[FlareOn4]IgniteMe1 https://files.buuoj.cn/files/02b39b8efca02367af23aa279c81cbec/attachment.zip 根据汇编语言分析 查看需要返回为1的函数 int sub_401050() {int v1; // [esp0h] [ebp-Ch]int i; // [esp4h] [ebp-8h]unsigned int j; // [esp4h] [ebp-8h]char v4; …...

基于“微店 Park”模式下 2+1 链动模式商城小程序的创新发展与应用研究

摘要&#xff1a;本文以“微店 Park”从“开店工具”向“众创平台”的转型为背景&#xff0c;深入探讨 21 链动模式商城小程序在该平台情境下的应用潜力与创新发展路径。通过剖析“微店 Park”的运营模式&#xff0c;包括灵活承租、低成本入驻、多元流量引流等特点&#xff0c;…...

C++11:【列表初始化】【右值引用和移动语义】

目录 一.列表初始化 1.1 C98传统的{} 1.2C11中的{} 1.3C中的std::initializer_list 二.右值引用和移动语义 2.1左值和右值 2.2左值引用和右值引用 2.3引用延长生命周期 2.4左值和右值的参数匹配 2.5右值引用和移动语义的使用场景 2.5.1左值引用主要使用场景 2.5.2移…...

Zookeeper的通知机制是什么?

大家好&#xff0c;我是锋哥。今天分享关于【Zookeeper的通知机制是什么?】面试题。希望对大家有帮助&#xff1b; Zookeeper的通知机制是什么? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Zookeeper的通知机制主要通过Watcher实现&#xff0c;它是Zookeeper客…...

嵌入式蓝桥杯学习1 电量LED

cubemx配置 1.新建一个STM32G431RBT6文件 2.在System-Core中点击SYS&#xff0c;找到Debug&#xff08;设置为Serial Wire&#xff09; 3.在System-Core中点击RCC&#xff0c;找到High Speed Clock(设置为Crystal/Ceramic Resonator) 4.打开Clock Configuration &#xff0…...

bsmap输出结果解释

关于, , -, --的解释 对应着参考基因组的正链&#xff08;有义链&#xff0c;非模板链&#xff0c;即hg38的序列&#xff0c;watson链&#xff09;&#xff1b; -代表正链的互补链&#xff08;正常情况下正链的互补链是负链&#xff0c;但在重硫酸盐处理后正链和负链并不互补…...

【java-数据结构篇】揭秘 Java LinkedList:链表数据结构的 Java 实现原理与核心概念

我的个人主页 我的专栏&#xff1a;Java-数据结构&#xff0c;希望能帮助到大家&#xff01;&#xff01;&#xff01;点赞❤ 收藏❤ 目录 1. Java LinkedList 基础 1.1 LinkedList 简介 1.2 LinkedList 的实现原理 1.3 LinkedList 与 ArrayList 的区别 2. 链表基础 2.1 链…...

macOS运行amd64的镜像

在macOS上运行amd64&#xff08;x86_64&#xff09;架构的镜像&#xff0c;通常通过虚拟化或仿真工具来实现。例如&#xff0c;如果你使用的是基于Apple Silicon&#xff08;M1或M2等&#xff09;芯片的Mac&#xff0c;那么你的处理器是ARM架构的&#xff0c;而amd64是x86架构&…...

轻量的基于图结构的RAG方案LightRAG

LightRAG出自2024年10月的论文《LIGHTRAG: SIMPLE AND FASTRETRIEVAL-AUGMENTED GENERATION》(github)&#xff0c;也是使用图结构来索引和搜索相关文本。 LightRAG作者认为已有的RAG系统有如下两个限制&#xff0c;导致难以回答类似"How does the rise of electric vehi…...

计算机的错误计算(一百七十三)

摘要 给定多项式 在 MATLAB 中计算 的值。输出是错误结果。 例1. 已知 计算 直接贴图吧&#xff1a; 这样&#xff0c;MATLAB 输出了错误结果。因为准确值为 0.2401e-16 . 注&#xff1a;可参看计算机的错误计算&#xff08;六&#xff09;。...

【力扣】—— 二叉树的前序遍历、字典序最小回文串

Hi~&#xff01;这里是奋斗的明志&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f331;&#x1f331;个人主页&#xff1a;奋斗的明志 &#x1f331;&#x1f331;所属专栏&#xff1a;数据结构 &#x1f4da;本系列文章为个人学…...

linux替换更高版本gcc

实际使用时对与gcc版本有很多要求, 需要在centos上安装更高版本的gcc 1、安装centos-release-scl sudo yum install centos-release-scl2、安装devtoolset&#xff0c;注意&#xff0c;如果想安装7.版本的&#xff0c;就改成devtoolset-7-gcc&#xff0c;以此类推 sudo yum …...

在Java中使用Apache POI导入导出Excel(六)

本文将继续介绍POI的使用&#xff0c;上接在Java中使用Apache POI导入导出Excel&#xff08;五&#xff09; 使用Apache POI组件操作Excel&#xff08;六&#xff09; 43、隐藏和取消隐藏行 使用 Excel&#xff0c;可以通过选择该行&#xff08;或行&#xff09;来隐藏工作表…...

`uni.setClipboardData` 是 uni-app 提供的一个 API 设置系统剪贴板的内容

uni.setClipboardData是uni-app提供的一个API&#xff0c;用于设置系统剪贴板的内容。 使用说明&#xff1a; 使用此API可以将指定的文本内容复制到系统剪贴板&#xff0c;使用户能够在其他应用或页面中粘贴这些内容。 uni.setClipboardData({data: , // 需要复制的内容 suc…...

量子计算数学基础:希尔伯特空间、张量积与密度算子核心解析

1. 量子计算的数学基石&#xff1a;从希尔伯特空间谈起搞量子计算&#xff0c;不管是做算法设计、硬件实现还是理论研究&#xff0c;绕不开的第一座大山就是它的数学语言。这不像经典编程&#xff0c;学个语法和数据结构就能上手。量子世界有自己的一套“语法规则”&#xff0c…...

告别调参噩梦!用Ball k-means在Python里5分钟搞定百万级数据聚类

百万级数据聚类的革命&#xff1a;用Ball k-means实现Python高效实战 当你的数据集膨胀到百万级别时&#xff0c;传统k-means算法突然变得像老牛拉车——迭代缓慢、调参困难、内存告急。我曾在一个电商用户分群项目中&#xff0c;面对120万条用户行为数据&#xff0c;sklearn的…...

LLM多智能体驱动微服务自治:从架构设计到Sock Shop实战评估

1. 项目概述&#xff1a;当微服务遇见大模型&#xff0c;自管理不再是空谈在云原生和微服务架构成为主流的今天&#xff0c;我们运维工程师面对的早已不是几台物理服务器&#xff0c;而是一个由成百上千个容器化服务实例构成的、动态且复杂的生态系统。服务间的调用链路像一张错…...

【2024播客降本增效终极方案】:单人团队如何用开源TTS实现月产60期高保真节目(附实测MOS分对比表)

更多请点击&#xff1a; https://codechina.net 第一章&#xff1a;AI语音合成在播客制作中的应用 AI语音合成技术正深刻重塑播客内容的生产流程&#xff0c;从脚本转语音、多角色配音到个性化音色定制&#xff0c;已实现端到端自动化与高质量听感的统一。相比传统录音方式&am…...

SLAM技术路线收敛?不,多模态融合正在重启路线之争

过去几年&#xff0c;SLAM技术路线确实呈现出明确的收敛趋势&#xff1a;纯视觉SLAM逐渐成熟&#xff0c;基于3DGS的实时建图成为新范式&#xff0c;激光SLAM也固化为工业场景的稳健选择。大家一度认为&#xff0c;算法架构的选择题已经做完。然而&#xff0c;多模态融合的深入…...

进程与线程:并发编程基础

摘要&#xff1a;进程与线程是操作系统面试的必考点&#xff0c;也是理解 AI 分布式训练和多线程数据加载的基础。本文从进程内存模型出发&#xff0c;系统讲解线程同步机制&#xff08;锁、信号量、条件变量&#xff09;&#xff0c;并通过 Python 代码展示多线程爬虫和生产者…...

工业AI落地:从数据冷启动到高质数据工程实战

1. 为什么“数据为中心”不是口号&#xff0c;而是工程现场的真实压力去年冬天&#xff0c;我帮一家做工业缺陷检测的初创公司做模型交付。他们拿来的数据集只有237张标注图&#xff0c;全是产线停机时人工拍的——光照不均、角度单一、连螺丝孔都只拍正面。当时团队信心满满&a…...

索尼360 Reality Audio发展受阻,苹果携手杜比让空间音频成主流

索尼的行动与失察索尼在市场创新方面思路正确&#xff0c;利用个人音频业务融入技术&#xff0c;争取平台采用&#xff0c;吸引音乐家录制专辑&#xff0c;授权音频制造商。但没料到自己不会成为沉浸式音频未来的关键参与者&#xff0c;失误只因不是苹果。空间音频如何定义2010…...

游戏模组革命:BepInEx框架让每个玩家都能打造个性化游戏体验

游戏模组革命&#xff1a;BepInEx框架让每个玩家都能打造个性化游戏体验 【免费下载链接】BepInEx Unity / XNA game patcher and plugin framework 项目地址: https://gitcode.com/GitHub_Trending/be/BepInEx 想要为心爱的游戏添加新功能、修改游戏机制&#xff0c;或…...

别再瞎试了!用Matlab手把手教你做拉丁超立方抽样(附10个点二维案例代码)

别再瞎试了&#xff01;用Matlab手把手教你做拉丁超立方抽样&#xff08;附10个点二维案例代码&#xff09; 当面对昂贵的仿真或物理实验时&#xff0c;如何用最少的样本点获取最全面的数据特征&#xff1f;传统随机抽样可能导致样本点扎堆或分布不均&#xff0c;而拉丁超立方抽…...