当前位置: 首页 > 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…...

【大模型微调】pdf转markdown

目前市面上大部分都是pdf文档,要想转换成能训练的文本,调研了各种工具。 觉得MinerU确实不错。 参考此链接进行操作 MinerU/docs/README_Ubuntu_CUDA_Acceleration_en_US.md at master opendatalab/MinerU GitHub 需要注意的几个点: 1. 使用root账户安装的,配置文件在…...

Vue 3 结合 TypeScript基本使用

Vue 3 结合 TypeScript 使用可以提供更加强大的类型检查和开发体验。以下是一些基本的步骤来开始使用 Vue 3 和 TypeScript&#xff1a; 1. 创建项目 你可以使用 Vue CLI 来快速创建一个支持 TypeScript 的 Vue 项目。首先确保你已经安装了 Node.js 和 npm。然后全局安装或更…...

Trotter steps的复杂性分析

总结 • 我们开发了使用汉密尔顿系数结构执行 Trotter 步骤的递归方法&#xff0c;超越了顺序方法。 • #Gate/Step 在汉密尔顿项数上是次线性的&#xff0c;而 #Step 仍然保持交换子缩放。 • 新结果给出了实空间中第二量化电子结构汉密尔顿的最快量子模拟。对第一量化量子模…...

mean,median,mode,var,std,min,max函数

剩余的函数都放在这篇里面吧 m e a n mean mean函数可以求平均值 a a a为向量时&#xff0c; m e a n ( a ) mean(a) mean(a)求向量中元素的平均值 a a a为矩阵时&#xff0c; m e a n ( a , 1 ) mean(a,1) mean(a,1)求矩阵中各列元素的平均值&#xff1b; m e a n ( a , 2 )…...

JavaScript实现tab栏切换

JavaScript实现tab栏切换 代码功能概述 这段代码实现了一个简单的选项卡&#xff08;Tab&#xff09;切换功能。它通过操作 HTML 元素的类名&#xff08;class&#xff09;来控制哪些选项卡&#xff08;Tab&#xff09;和对应的内容板块显示&#xff0c;哪些隐藏。基本思路是先…...

精确电压输出,家电和工业设备的完美选择,宽输入电压线性稳压器

WD5201线性稳压器的核心内容概述&#xff1a; 主要特点 • 高精度输出电压&#xff1a;2%精度。 • 输出电压可调&#xff1a;支持5V、3.3V、2.7V三档输出。 • 优化控制方式&#xff1a;提升效率。 • 宽输入电压范围&#xff1a;80305VAC。 • 无需功率电感和输入高压电…...

深入理解定时器:优先队列与时间轮实现

文章目录 1. 线程池概述线程池的基本特点&#xff1a; 2. 使用线程池的优先队列定时器实现2.1 优先队列定时器实现2.2 解释&#xff1a; 3. 使用时间轮的线程池定时器实现3.1 时间轮定时器实现 4. 总结 在定时器设计中&#xff0c;使用线程池来执行定时任务可以有效提高程序的性…...

autogen-agentchat 0.4.0.dev8版本的安装

1. 安装命令 pip install autogen-agentchat0.4.0.dev8 autogen-ext[openai]0.4.0.dev82. 版本检查 import autogen_agentchat print(autogen_agentchat.__version__)0.4.0.dev8import autogen_ext print(autogen_ext.__version__)0.4.0.dev83. 第一个案例 使用 autogen-age…...

JAVA |日常开发中读写XML详解

JAVA &#xff5c;日常开发中读写XML详解 前言一、XML 简介二、在 Java 中读取 XML2.1 使用 DOM&#xff08;Document Object Model&#xff09;方式读取 XML2.2 使用 SAX&#xff08;Simple API for XML&#xff09;方式读取 XML 三、在 Java 中写入 XML3.1 使用 DOM 方式写入…...

React 路由与组件通信:如何实现路由参数、查询参数、state和上下文的使用

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…...