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

A*(Astar)算法详解与应用

算法背景

        A*(A-Star)算法是一种在图形平面上,有多个节点的路径中,求出最低通过成本的算法。其历史可以追溯到早期的图搜索算法,如Dijkstra算法和贪心最佳优先搜索(Greedy Best-First Search)。是人工智能、游戏开发、机器人路径规划等领域中最经典、最有效的寻路算法之一。

算法解释

        A*算法的核心思想是结合了Dijkstra算法和Best-First-Search(BFS)算法的特点。他给每个格子赋予了一个权重 f(n) = g(n) + h(n) ,通过权重来决定遍历的顺序。

详细解释

        首先得理解广度优先搜索(BFS),在方格中,BFS通过遍历起始格子上下左右的四个格子,将其压入一个队列,通过循环 出队-搜索-入队 来寻找最短路。

        而A*算法则是在BFS的基础上使用了贪心策略,给每个格子赋予一个权重f(n),其中f(n) = g(n) + h(n) 。

        -  g(n)为起点到当前点的最短距离

        -  h(n)为当前点到终点的估计距离

        A*算法则使用了优先队列,通过寻找路径代价最小的节点来寻找目标点,它保证了如果h(n)的估计值是准确的,那么找到的路径也是最短的。同时,A*算法比BFS减少了遍历点的数量,加快了路线寻找的速度。

        【很好理解,当你要从一个地方到另一个地方,并且你已经知道终点的方位,及时没有导航,你很自然会优先朝着终点方向前进,即使和道路方向并不相同】

        下图是每个格子的 g(n) 也就是当前距离起点的步数,这个很好理解。

        下图是每个格子的 h(n) ,也就是每个格子距离终点的估计距离,下图使用了曼哈顿距离

        你会发现,在上图没有障碍的时候,每个格子的权重是一样的,无法体现路线的优化。如果使用欧几里得距离,就能体现出权重的作用,如下图:

距离的估计

        有很多计算方式,比如:

1.曼哈顿距离

  • 定义:只能沿着网格的横纵方向移动,不能斜向移动。                  

 D_{Manhattan}=\left | x_{1}-x_{2} \right |+\left | y_{1}-y_{2} \right |

  • 适用场景:城市街区网格、只能走直线(上下左右)的环境,比如A*算法中常用的启发函数

  • 特点:简单、计算快,但可能高估实际距离。

2.欧几里得距离:

  • 定义:两点之间的“直线距离”,也就是我们日常生活中说的“最短距离”。

D_{Euclidean}=\sqrt{(x_{1}-x_{2})^{2}+(y_{1}-y_{2})^{2}}

  • 适用场景:连续空间、物理世界中的距离计算,比如机器人导航、图像识别。

  • 特点:真实距离,但计算稍慢,且可能包含浮点运算。

3. 切比雪夫距离:

  • 定义:允许八方向移动(上下左右+对角线),取各坐标差值的最大值。

D_{Chebyshev}=max(\left | x_{1}-x_{2} \right |,\left | y_{1}-y_{2} \right |)

  • 适用场景:可以斜向移动的游戏地图(如象棋中的国王移动),或某些特殊路径规划。

  • 特点:比曼哈顿更灵活,但可能低估实际步数。

 4. 八方向距离:

  • 定义:允许八方向移动,但斜向移动的代价更高(比如√2倍),更接近真实情况。

D_{Octile}= max(\left | x_{1}-x_{2} \right |,\left | y_{1}-y_{2} \right |)\times\sqrt{2}+min(\left | x_{1}-x_{2} \right |,\left | y_{1}-y_{2} \right |)\times(1-\sqrt{2})

        近似写法:

 D_{Octile}\approx max(\left | dx \right |,\left | dy \right |)\times\sqrt{2}+(\left | max(\left | dx \right |,\left | dy \right |)-min(\left | dx \right |,\left | dy \right |) \right |)

  • 适用场景:允许斜向移动但代价更高的地图,比如某些策略游戏或真实地形路径规划。

  • 特点:比切比雪夫更精确,适合八方向移动的A*启发函数。

最短路证明

        所以,A*算法得到的路径一定是最短路吗?答案是否定的。

        我们已知BFS得到的路径一定是最短路,但是BFS的时间效率过低,我们添加了贪心的策略,权衡速度与最优性。

        但是,如果我们处理好启发式函数的大小,可以保证A*路径的最优性,即启发项小于等于最优的那条路径

        h(n)\leq ^{*}h(n)

        在之前的方格示例中,A*得到的一定是最优路径,因为每个点的估计价值是通过当前点与终点的相对位置计算得出的,其有绝对性,即不同点的估计价值在同个距离算法下是绝对的。

        但是在实际应用中,直接使用相对坐标来计算估计价值是不合理的,比如:

        其中蓝色的为河流,我们预估通过河流所需时间需要10个单位,假设通过一个格子所需时间1个单位。

        从起点开始,到河流的权重f(n)为16,而走上面路线权重为14,则走上面路线到达终点。但是事实上河流上有桥,可以直接通过,而河流路径明显比上面更短,丧失了最优性

        发现当h<=4的时候,其最短路径都是正确的,这就是启发项大小要小于等于最优路径。

        在实际情况中,要合理设置初始权值,才能避免某些不必要的麻烦。

        当然,比如你想要某个敌人不能走某条路,直接将初始权值设为无限大,就可以实现该功能。

算法示例

        写了一个html文件,可以直观展现A*算法的过程与结果,可以复制到本地尝试一下:

  代码:

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>A* 算法可视化演示(优化版)</title><style>body {font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;background-color: #f5f5f5;margin: 0;padding: 20px;color: #333;display: flex;flex-direction: column;align-items: center;}h1 {color: #2c3e50;margin-bottom: 10px;}.description {max-width: 700px;text-align: center;margin-bottom: 20px;color: #555;}.controls {margin-bottom: 20px;display: flex;gap: 10px;flex-wrap: wrap;justify-content: center;align-items: center;}button {padding: 10px 15px;border: none;border-radius: 5px;background-color: #3498db;color: white;cursor: pointer;font-size: 16px;transition: background-color 0.3s;}button:hover {background-color: #2980b9;}button:disabled {background-color: #bdc3c7;cursor: not-allowed;}.grid-container {display: flex;flex-direction: column;align-items: center;margin-bottom: 20px;}.grid {display: grid;grid-template-columns: repeat(20, 25px);grid-template-rows: repeat(20, 25px);gap: 1px;border: 1px solid #ccc;background-color: #ddd;position: relative;}.cell {width: 25px;height: 25px;background-color: #fff;display: flex;align-items: center;justify-content: center;cursor: pointer;font-size: 10px;transition: background-color 0.2s;position: relative;}.cell:hover {opacity: 0.8;}.wall { background-color: #333; }.start { background-color: #2ecc71; }.end { background-color: #e74c3c; }.visited { background-color: #3498db; }.path { background-color: #f1c40f; }.current { background-color: #9b59b6; }.frontier { background-color: #95a5a6; opacity: 0.6; }.legend {display: flex;gap: 15px;margin-top: 10px;font-size: 14px;flex-wrap: wrap;}.legend-item {display: flex;align-items: center;gap: 5px;}.legend-color {width: 15px;height: 15px;border: 1px solid #ccc;}.info {margin-top: 20px;max-width: 600px;background-color: white;padding: 15px;border-radius: 5px;box-shadow: 0 2px 5px rgba(0,0,0,0.1);}.info h3 {margin-top: 0;color: #2c3e50;}.info p {margin: 5px 0;font-size: 14px;}.mode-selector {margin-bottom: 10px;display: flex;gap: 10px;align-items: center;}select, input {padding: 5px;font-size: 16px;}.speed-control {display: flex;align-items: center;gap: 10px;}.stats {display: flex;gap: 20px;flex-wrap: wrap;}.stat-item {font-size: 14px;}.stat-value {font-weight: bold;color: #3498db;}.heuristic-selector {display: flex;align-items: center;gap: 10px;}</style>
</head>
<body><h1>A* 寻路算法可视化(优化版)</h1><div class="description">点击网格设置起点(绿色)、终点(红色)和障碍物(黑色)。支持拖拽绘制障碍物,可切换不同启发式函数。</div><div class="mode-selector"><label for="mode">编辑模式: </label><select id="mode"><option value="start">设置起点</option><option value="end">设置终点</option><option value="wall">绘制障碍墙</option><option value="erase">擦除障碍</option></select><div class="heuristic-selector"><label for="heuristic">启发式函数: </label><select id="heuristic"><option value="manhattan">曼哈顿距离</option><option value="euclidean">欧几里得距离</option><option value="chebyshev">切比雪夫距离</option><option value="octile">八方向距离</option></select></div></div><div class="controls"><button id="startBtn">开始寻路</button><button id="stepBtn" disabled>单步执行</button><button id="autoBtn" disabled>自动播放</button><button id="resetBtn">重置网格</button><button id="clearPathBtn">清除路径</button><button id="mazeBtn">生成迷宫</button><div class="speed-control"><label for="speed">速度: </label><input type="range" id="speed" min="10" max="500" value="100" step="10"><span id="speedValue">100ms</span></div></div><div class="grid-container"><div class="grid" id="grid"></div><div class="legend"><div class="legend-item"><div class="legend-color start"></div><span>起点</span></div><div class="legend-item"><div class="legend-color end"></div><span>终点</span></div><div class="legend-item"><div class="legend-color wall"></div><span>障碍</span></div><div class="legend-item"><div class="legend-color frontier"></div><span>待探索</span></div><div class="legend-item"><div class="legend-color visited"></div><span>已访问</span></div><div class="legend-item"><div class="legend-color current"></div><span>当前节点</span></div><div class="legend-item"><div class="legend-color path"></div><span>最终路径</span></div></div></div><div class="info"><h3>算法信息</h3><p id="currentInfo">设置起点和终点,然后点击"开始寻路"。</p><div class="stats"><div class="stat-item">路径长度: <span class="stat-value" id="pathLength">-</span></div><div class="stat-item">访问节点数: <span class="stat-value" id="visitedCount">-</span></div><div class="stat-item">开放列表大小: <span class="stat-value" id="openListSize">-</span></div><div class="stat-item">执行时间: <span class="stat-value" id="executionTime">-</span></div></div></div><script>// 网格配置const ROWS = 20;const COLS = 20;let grid = [];let startCell = null;let endCell = null;let isRunning = false;let isAutoRunning = false;let openList = [];let closedSet = new Set(); // 使用Set提高查找效率let path = [];let currentAlgorithmState = null;let autoInterval = null;let isMouseDown = false;let startTime = 0;let animationSpeed = 100;// 优先队列实现(最小堆)class PriorityQueue {constructor() {this.heap = [];}push(element) {this.heap.push(element);this.bubbleUp(this.heap.length - 1);}pop() {if (this.heap.length === 0) return null;const min = this.heap[0];const end = this.heap.pop();if (this.heap.length > 0) {this.heap[0] = end;this.bubbleDown(0);}return min;}bubbleUp(index) {while (index > 0) {const parentIndex = Math.floor((index - 1) / 2);if (this.heap[index].f < this.heap[parentIndex].f) {[this.heap[index], this.heap[parentIndex]] = [this.heap[parentIndex], this.heap[index]];index = parentIndex;} else {break;}}}bubbleDown(index) {while (true) {let minIndex = index;const leftChild = 2 * index + 1;const rightChild = 2 * index + 2;if (leftChild < this.heap.length && this.heap[leftChild].f < this.heap[minIndex].f) {minIndex = leftChild;}if (rightChild < this.heap.length && this.heap[rightChild].f < this.heap[minIndex].f) {minIndex = rightChild;}if (minIndex !== index) {[this.heap[index], this.heap[minIndex]] = [this.heap[minIndex], this.heap[index]];index = minIndex;} else {break;}}}get length() {return this.heap.length;}contains(element) {return this.heap.includes(element);}update(element) {const index = this.heap.indexOf(element);if (index !== -1) {this.bubbleUp(index);this.bubbleDown(index);}}}// 初始化网格function initGrid() {const gridElement = document.getElementById('grid');gridElement.innerHTML = '';grid = [];for (let row = 0; row < ROWS; row++) {const gridRow = [];for (let col = 0; col < COLS; col++) {const cell = document.createElement('div');cell.className = 'cell';cell.dataset.row = row;cell.dataset.col = col;cell.addEventListener('mousedown', () => {isMouseDown = true;handleCellClick(row, col);});cell.addEventListener('mouseenter', () => {if (isMouseDown) {handleCellDrag(row, col);}});cell.addEventListener('mouseup', () => {isMouseDown = false;});gridElement.appendChild(cell);gridRow.push({element: cell,row,col,isWall: false,f: Infinity,g: Infinity,h: 0,parent: null,inOpenList: false});}grid.push(gridRow);}// 全局鼠标释放事件document.addEventListener('mouseup', () => {isMouseDown = false;});resetAlgorithmState();}function resetAlgorithmState() {openList = new PriorityQueue();closedSet = new Set();path = [];isRunning = false;isAutoRunning = false;currentAlgorithmState = null;startTime = 0;document.getElementById('startBtn').disabled = false;document.getElementById('stepBtn').disabled = true;document.getElementById('autoBtn').disabled = true;clearInterval(autoInterval);updateInfoText('设置起点和终点,然后点击"开始寻路"。');updateStats();// 清除之前的访问和路径标记,但保留墙、起点和终点for (let row = 0; row < ROWS; row++) {for (let col = 0; col < COLS; col++) {const cell = grid[row][col];cell.element.classList.remove('visited', 'path', 'current', 'frontier');cell.f = Infinity;cell.g = Infinity;cell.h = 0;cell.parent = null;cell.inOpenList = false;}}}function handleCellClick(row, col) {if (isRunning) return;const cell = grid[row][col];const mode = document.getElementById('mode').value;switch (mode) {case 'start':if (startCell) {startCell.element.classList.remove('start');}cell.element.classList.remove('wall', 'end');cell.element.classList.add('start');cell.isWall = false;startCell = cell;break;case 'end':if (endCell) {endCell.element.classList.remove('end');}cell.element.classList.remove('wall', 'start');cell.element.classList.add('end');cell.isWall = false;endCell = cell;break;case 'wall':if (cell !== startCell && cell !== endCell) {cell.isWall = true;cell.element.classList.add('wall');}break;case 'erase':if (cell !== startCell && cell !== endCell) {cell.isWall = false;cell.element.classList.remove('wall');}break;}}function handleCellDrag(row, col) {const cell = grid[row][col];const mode = document.getElementById('mode').value;if (mode === 'wall' && cell !== startCell && cell !== endCell) {cell.isWall = true;cell.element.classList.add('wall');} else if (mode === 'erase' && cell !== startCell && cell !== endCell) {cell.isWall = false;cell.element.classList.remove('wall');}}// 启发式函数function heuristic(a, b) {const type = document.getElementById('heuristic').value;const dx = Math.abs(a.row - b.row);const dy = Math.abs(a.col - b.col);switch (type) {case 'manhattan':return dx + dy;case 'euclidean':return Math.sqrt(dx * dx + dy * dy);case 'chebyshev':return Math.max(dx, dy);case 'octile':return Math.max(dx, dy) + (Math.sqrt(2) - 1) * Math.min(dx, dy);default:return dx + dy;}}function getNeighbors(cell) {const neighbors = [];const { row, col } = cell;// 八个方向(包括对角线)const directions = [[-1, 0, 1], [0, 1, 1], [1, 0, 1], [0, -1, 1],  // 上右下左[-1, -1, 1.414], [-1, 1, 1.414], [1, 1, 1.414], [1, -1, 1.414]  // 对角线];const allowDiagonal = document.getElementById('heuristic').value !== 'manhattan';const limit = allowDiagonal ? 8 : 4;for (let i = 0; i < limit; i++) {const [dr, dc, cost] = directions[i];const newRow = row + dr;const newCol = col + dc;if (newRow >= 0 && newRow < ROWS && newCol >= 0 && newCol < COLS) {const neighbor = grid[newRow][newCol];if (!neighbor.isWall) {// 对角线移动时检查两边是否有墙if (i >= 4) {const side1 = grid[row + directions[i-4][0]][col + directions[i-4][1]];const side2 = grid[row + directions[(i-3)%4][0]][col + directions[(i-3)%4][1]];if (side1.isWall || side2.isWall) continue;}neighbors.push({cell: neighbor, cost});}}}return neighbors;}function startAlgorithm() {if (!startCell || !endCell) {alert('请先设置起点和终点!');return;}resetAlgorithmState();isRunning = true;startTime = performance.now();// 初始化开放列表,加入起点openList = new PriorityQueue();startCell.g = 0;startCell.h = heuristic(startCell, endCell);startCell.f = startCell.h;startCell.inOpenList = true;openList.push(startCell);document.getElementById('startBtn').disabled = true;document.getElementById('stepBtn').disabled = false;document.getElementById('autoBtn').disabled = false;currentAlgorithmState = {found: false,noPath: false};updateStats();updateInfoText('A* 算法已启动,准备开始搜索...');}function stepAlgorithm() {if (!isRunning || currentAlgorithmState.found || currentAlgorithmState.noPath) return;if (openList.length === 0) {currentAlgorithmState.noPath = true;updateInfoText('搜索完成:没有找到路径。');finishAlgorithm();return;}// 从优先队列中取出f值最小的节点const current = openList.pop();current.inOpenList = false;// 标记当前节点if (current !== startCell && current !== endCell) {current.element.classList.add('current');current.element.classList.remove('frontier');if (current.parent && current.parent !== startCell) {current.parent.element.classList.remove('current');}}// 如果找到终点if (current === endCell) {currentAlgorithmState.found = true;reconstructPath(current);const executionTime = (performance.now() - startTime).toFixed(2);updateInfoText(`搜索完成:已找到最短路径!用时 ${executionTime}ms`);finishAlgorithm();return;}// 将当前节点移到关闭列表closedSet.add(current);if (current !== startCell && current !== endCell) {current.element.classList.remove('current');current.element.classList.add('visited');}updateInfoText(`正在检查节点 (${current.row}, ${current.col}) - f=${current.f.toFixed(2)}`);// 检查所有邻居const neighbors = getNeighbors(current);for (const {cell: neighbor, cost} of neighbors) {if (closedSet.has(neighbor)) continue;const tentativeG = current.g + cost;if (tentativeG < neighbor.g) {neighbor.parent = current;neighbor.g = tentativeG;neighbor.h = heuristic(neighbor, endCell);neighbor.f = neighbor.g + neighbor.h;if (!neighbor.inOpenList) {openList.push(neighbor);neighbor.inOpenList = true;if (neighbor !== endCell) {neighbor.element.classList.add('frontier');}} else {openList.update(neighbor);}}}updateStats();}function reconstructPath(current) {path = [];let temp = current;while (temp) {path.push(temp);temp = temp.parent;}path.reverse();// 动画显示路径path.forEach((cell, index) => {if (cell !== startCell && cell !== endCell) {setTimeout(() => {cell.element.classList.add('path');cell.element.classList.remove('visited');}, index * 20);}});}function finishAlgorithm() {isRunning = false;isAutoRunning = false;document.getElementById('stepBtn').disabled = true;document.getElementById('autoBtn').disabled = true;clearInterval(autoInterval);// 移除所有当前标记document.querySelectorAll('.current').forEach(el => el.classList.remove('current'));document.querySelectorAll('.frontier').forEach(el => el.classList.remove('frontier'));const executionTime = (performance.now() - startTime).toFixed(2);document.getElementById('executionTime').textContent = `${executionTime}ms`;document.getElementById('pathLength').textContent = path.length > 0 ? path.length - 1 : '-';}function startAutoRun() {if (isAutoRunning) {stopAutoRun();return;}isAutoRunning = true;document.getElementById('autoBtn').textContent = '停止自动播放';autoInterval = setInterval(() => {if (currentAlgorithmState.found || currentAlgorithmState.noPath) {stopAutoRun();return;}stepAlgorithm();}, animationSpeed);}function stopAutoRun() {isAutoRunning = false;clearInterval(autoInterval);document.getElementById('autoBtn').textContent = '自动播放';}function updateInfoText(text) {document.getElementById('currentInfo').textContent = text;}function updateStats() {document.getElementById('visitedCount').textContent = closedSet.size;document.getElementById('openListSize').textContent = openList.length;document.getElementById('pathLength').textContent = path.length > 0 ? path.length - 1 : '-';}// 生成随机迷宫function generateMaze() {if (isRunning) return;// 清除所有墙for (let row = 0; row < ROWS; row++) {for (let col = 0; col < COLS; col++) {const cell = grid[row][col];if (cell !== startCell && cell !== endCell) {cell.isWall = false;cell.element.classList.remove('wall');}}}// 随机生成障碍物(30%概率)for (let row = 0; row < ROWS; row++) {for (let col = 0; col < COLS; col++) {const cell = grid[row][col];if (cell !== startCell && cell !== endCell && Math.random() < 0.3) {cell.isWall = true;cell.element.classList.add('wall');}}}resetAlgorithmState();}// 速度控制document.getElementById('speed').addEventListener('input', (e) => {animationSpeed = parseInt(e.target.value);document.getElementById('speedValue').textContent = `${animationSpeed}ms`;if (isAutoRunning) {stopAutoRun();startAutoRun();}});// 事件监听器document.getElementById('startBtn').addEventListener('click', startAlgorithm);document.getElementById('stepBtn').addEventListener('click', stepAlgorithm);document.getElementById('autoBtn').addEventListener('click', startAutoRun);document.getElementById('resetBtn').addEventListener('click', initGrid);document.getElementById('clearPathBtn').addEventListener('click', resetAlgorithmState);document.getElementById('mazeBtn').addEventListener('click', generateMaze);// 初始化initGrid();// 设置默认起点和终点handleCellClick(5, 2);document.getElementById('mode').value = 'end';handleCellClick(15, 17);document.getElementById('mode').value = 'wall';</script>
</body>
</html>
http://www.dtcms.com/a/365187.html

相关文章:

  • 【C++八股文】数据结构篇
  • Vue Vapor 事件机制深潜:从设计动机到源码解析
  • Windows 电源管理和 Shutdown 命令详解
  • QuillBot:AI文本重写神器(附官网),高效解决文案润色与语法检查需求
  • 不只会修图!谷歌发布官方指南,教你用 Nano Banana 玩转文生图
  • Mysql数据库(性能)索引学习
  • 如何获取easy-ui的表格的分页大小
  • 创建Spring MVC和注解
  • 企业资源计划(ERP)系统:数字化企业的核心引擎
  • 数据结构——顺序表和单向链表(2)
  • MybatisPlus 根据实体类获取对应的Mapper
  • 硬件开发1-51单片机2-按键、中断
  • Process Lasso:高效管理和优化计算机进程
  • 并查集_路径压缩
  • [嵌入式embed][Qt]Qt5.12+Opencv4.x+Cmake4.x_用Qt编译linux-Opencv库 测试
  • Linux 用户的 Windows 改造之旅
  • linux命名管道的使用
  • 关于linux数据库编程——sqlite3
  • Unity 中 打包 assetsBundle
  • C语言字符函数和字符串函数(1)
  • 《网络安全实战:CC攻击(应用层)与DDoS攻击(网络层)的底层逻辑与防御体系》​
  • 基于SpringBoot+Vue开发的环境保护监督管理网站
  • 如何通过控制台查看向量检索服务使用数据
  • Vue Router原理及SPA页面刷新解析
  • 融云:当我们谈论 AI 重构业务时,我们到底在谈论什么
  • SAM TTS网页官网入口 – 在线版微软tts在线语音合成助手
  • 【TRAE调教指南之MCP篇】FastMCP:快速制作自己的MCP服务
  • 对锁的总结
  • Agent 热潮遇冷?Manus 为何仍是 “版本神”
  • 充电枪结构设计-经验总结