JS实现高效导航——A*寻路算法+导航图简化法
一、如何实现两点间路径导航
导航实现的通用步骤,一般是:
1、网格划分
将地图划分为网格,即例如地图是一张图片,其像素为1000*1000,那我们将此图片划分为各个10*10的网格,从而提高寻路算法的计算量。
2、标记障碍物
把地图划分的网格用一个二位数组存储,若此网格中有障碍物则标记为1,若可通行则标记为0,例如[[0],[1]]。
3、将网格简化为路径节点(此步可省)
之前是基于网格进行路径计算,但为了减少A*算法的计算量,可以将网格地图简化为“路标形式”,这种方法通常通过提取关键节点并构建稀疏图代替密集的网格图来实现。
4、使用A*算法寻找最短路径
二、A*(A-Star)寻路算法
A*寻路算法是一种用于路径规划的经典算法,广泛应用于游戏开发、机器人导航和地图路径计算等领域。它是一种基于图搜索的启发式算法,可以高效地在网格或节点图中找到从起点到终点的最短路径(或代价最低路径)。
1、算法核心:如何搜索最短路径
A*算法通过综合考虑两方面的代价来进行路径搜索:
- 实际代价(G):从起点到当前节点的实际路径代价。
- 估计代价(H):从当前节点到终点的启发式估计代价,通常采用欧几里得距离、曼哈顿距离等方法。
- 总代价(F):
F = G + H
,即从起点经过当前节点,到终点的总估计代价。
2、算法步骤
-
初始
- 将起点加入“打开列表”(待处理的节点列表)。
- “关闭列表”为空(已处理的节点列表)。
-
搜索
重复以下步骤,直到找到终点或打开列表为空:- 从打开列表中选择
F
值最小的节点作为当前节点。 - 如果当前节点是终点,路径找到,结束。
- 否则,将当前节点从打开列表移至关闭列表。
- 对当前节点的所有邻居节点:
- 如果邻居在关闭列表中,跳过。
- 如果邻居不可通行(障碍物),跳过。
- 如果邻居不在打开列表中,计算其
F
值,设置父节点为当前节点,并加入打开列表。 - 如果邻居已在打开列表中,检查是否可以通过当前节点降低
G
值;若可以,更新其F
值和父节点。
- 从打开列表中选择
-
路径回溯
如果找到终点,通过父节点逐步回溯,得到路径。
3、启发函数的选择
启发函数是 A* 的关键。常用的启发函数有:
- 曼哈顿距离(适合网格地图,不能斜走时使用)
- 欧几里得距离(适合允许对角线走法的地图)
- 对角线距离(适合允许斜走但代价固定的地图)
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*寻路算法+导航图简化法
一、如何实现两点间路径导航 导航实现的通用步骤,一般是: 1、网格划分 将地图划分为网格,即例如地图是一张图片,其像素为1000*1000,那我们将此图片划分为各个10*10的网格,从而提高寻路算法的计算量。 2、标…...

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

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

泷羽sec:shell编程(9)不同脚本的互相调用和重定向操作
声明: 学习视频来自B站up主 泷羽sec 有兴趣的师傅可以关注一下,如涉及侵权马上删除文章,笔记只是方便各位师傅的学习和探讨,文章所提到的网站以及内容,只做学习交流,其他均与本人以及泷羽sec团队无关&#…...

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

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

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

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

嵌入式蓝桥杯学习1 电量LED
cubemx配置 1.新建一个STM32G431RBT6文件 2.在System-Core中点击SYS,找到Debug(设置为Serial Wire) 3.在System-Core中点击RCC,找到High Speed Clock(设置为Crystal/Ceramic Resonator) 4.打开Clock Configuration ࿰…...

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

【java-数据结构篇】揭秘 Java LinkedList:链表数据结构的 Java 实现原理与核心概念
我的个人主页 我的专栏:Java-数据结构,希望能帮助到大家!!!点赞❤ 收藏❤ 目录 1. Java LinkedList 基础 1.1 LinkedList 简介 1.2 LinkedList 的实现原理 1.3 LinkedList 与 ArrayList 的区别 2. 链表基础 2.1 链…...
macOS运行amd64的镜像
在macOS上运行amd64(x86_64)架构的镜像,通常通过虚拟化或仿真工具来实现。例如,如果你使用的是基于Apple Silicon(M1或M2等)芯片的Mac,那么你的处理器是ARM架构的,而amd64是x86架构&…...

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

计算机的错误计算(一百七十三)
摘要 给定多项式 在 MATLAB 中计算 的值。输出是错误结果。 例1. 已知 计算 直接贴图吧: 这样,MATLAB 输出了错误结果。因为准确值为 0.2401e-16 . 注:可参看计算机的错误计算(六)。...

【力扣】—— 二叉树的前序遍历、字典序最小回文串
Hi~!这里是奋斗的明志,很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~~ 🌱🌱个人主页:奋斗的明志 🌱🌱所属专栏:数据结构 📚本系列文章为个人学…...
linux替换更高版本gcc
实际使用时对与gcc版本有很多要求, 需要在centos上安装更高版本的gcc 1、安装centos-release-scl sudo yum install centos-release-scl2、安装devtoolset,注意,如果想安装7.版本的,就改成devtoolset-7-gcc,以此类推 sudo yum …...
在Java中使用Apache POI导入导出Excel(六)
本文将继续介绍POI的使用,上接在Java中使用Apache POI导入导出Excel(五) 使用Apache POI组件操作Excel(六) 43、隐藏和取消隐藏行 使用 Excel,可以通过选择该行(或行)来隐藏工作表…...
`uni.setClipboardData` 是 uni-app 提供的一个 API 设置系统剪贴板的内容
uni.setClipboardData是uni-app提供的一个API,用于设置系统剪贴板的内容。 使用说明: 使用此API可以将指定的文本内容复制到系统剪贴板,使用户能够在其他应用或页面中粘贴这些内容。 uni.setClipboardData({data: , // 需要复制的内容 suc…...

Zustand 状态管理库:极简而强大的解决方案
Zustand 是一个轻量级、快速和可扩展的状态管理库,特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...
可靠性+灵活性:电力载波技术在楼宇自控中的核心价值
可靠性灵活性:电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中,电力载波技术(PLC)凭借其独特的优势,正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据,无需额外布…...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版分享
平时用 iPhone 的时候,难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵,或者买了二手 iPhone 却被原来的 iCloud 账号锁住,这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)
宇树机器人多姿态起立控制强化学习框架论文解析 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一) 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...
鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/
使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题:docker pull 失败 网络不同,需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

优选算法第十二讲:队列 + 宽搜 优先级队列
优选算法第十二讲:队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...
Fabric V2.5 通用溯源系统——增加图片上传与下载功能
fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...

windows系统MySQL安装文档
概览:本文讨论了MySQL的安装、使用过程中涉及的解压、配置、初始化、注册服务、启动、修改密码、登录、退出以及卸载等相关内容,为学习者提供全面的操作指导。关键要点包括: 解压 :下载完成后解压压缩包,得到MySQL 8.…...