在游戏开发、机器人导航和地图应用中,A*(A-star)算法是最经典且高效的路径搜索算法之一。本教程将带你用Rust语言从零开始实现一个完整的A*算法,即使你是编程新手,也能轻松理解并掌握!
A*算法是一种启发式搜索算法,它结合了Dijkstra算法的“最短路径优先”和贪心算法的“目标导向”思想。每个节点的评估函数为:
f(n) = g(n) + h(n)
g(n):从起点到当前节点的实际代价h(n):从当前节点到终点的预估代价(启发函数)f(n):总代价,用于决定搜索顺序首先,确保你已安装Rust(可通过 rustup 安装)。然后创建新项目:
cargo new astar_tutorialcd astar_tutorial 我们需要几个关键结构体来表示地图、节点和状态。
// src/main.rs#[derive(Clone, Copy, PartialEq, Eq, Hash)]pub struct Point { pub x: i32, pub y: i32,}#[derive(Clone)]pub struct Node { pub point: Point, pub g_cost: u32, // 起点到当前点的实际代价 pub h_cost: u32, // 启发函数估算值 pub f_cost: u32, // f = g + h pub parent: Option<Point>,}impl Node { pub fn new(point: Point, g: u32, h: u32, parent: Option<Point>) -> Self { Node { point, g_cost: g, h_cost: h, f_cost: g + h, parent, } }} 我们使用曼哈顿距离作为启发函数(适用于只能上下左右移动的网格):
fn manhattan_distance(a: Point, b: Point) -> u32 { (a.x.abs_diff(b.x) + a.y.abs_diff(b.y)) as u32} 现在,我们将实现完整的A*搜索逻辑。我们会用BinaryHeap作为优先队列(需自定义排序),并用HashSet记录已访问节点。
use std::collections::{BinaryHeap, HashSet};use std::cmp::Ordering;// 为了让BinaryHeap按f_cost最小优先,需反转Ord实现impl Ord for Node { fn cmp(&self, other: &Self) -> Ordering { // 注意:BinaryHeap是最大堆,所以我们要反向比较 other.f_cost.cmp(&self.f_cost) .then_with(|| self.g_cost.cmp(&other.g_cost)) }}impl PartialOrd for Node { fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }}pub fn astar_search( grid: &Vec<Vec<bool>>, // true表示可通过,false表示障碍 start: Point, end: Point,) -> Option<Vec<Point>> { let rows = grid.len() as i32; let cols = grid[0].len() as i32; // 边界检查 if !is_valid_point(start, rows, cols) || !is_valid_point(end, rows, cols) || !grid[start.y as usize][start.x as usize] || !grid[end.y as usize][end.x as usize] { return None; } let mut open_set = BinaryHeap::new(); let mut closed_set: HashSet<Point> = HashSet::new(); let start_node = Node::new(start, 0, manhattan_distance(start, end), None); open_set.push(start_node); // 四个方向:上、下、左、右 let directions = [Point { x: 0, y: -1 }, Point { x: 0, y: 1 }, Point { x: -1, y: 0 }, Point { x: 1, y: 0 }]; while let Some(current) = open_set.pop() { if current.point == end { // 重建路径 return reconstruct_path(current, &open_set); } closed_set.insert(current.point); for dir in &directions { let neighbor = Point { x: current.point.x + dir.x, y: current.point.y + dir.y, }; // 检查邻居是否有效 if !is_valid_point(neighbor, rows, cols) || !grid[neighbor.y as usize][neighbor.x as usize] || closed_set.contains(&neighbor) { continue; } let tentative_g = current.g_cost + 1; // 检查是否已在open_set中且有更优路径 let mut skip = false; let mut to_remove = None; for (i, node) in open_set.iter().enumerate() { if node.point == neighbor && tentative_g >= node.g_cost { skip = true; break; } } if skip { continue; } // 移除旧节点(简化实现,实际可优化) open_set.retain(|n| n.point != neighbor); let h = manhattan_distance(neighbor, end); let neighbor_node = Node::new(neighbor, tentative_g, h, Some(current.point)); open_set.push(neighbor_node); } } None // 未找到路径}fn is_valid_point(p: Point, rows: i32, cols: i32) -> bool { p.x >= 0 && p.x < cols && p.y >= 0 && p.y < rows}fn reconstruct_path(mut end_node: Node, open_set: &BinaryHeap<Node>) -> Option<Vec<Point>> { let mut path = vec![end_node.point]; let mut current = end_node.parent; // 从open_set中查找父节点(简化版,实际建议用HashMap存储) while let Some(parent_point) = current { path.push(parent_point); // 在真实项目中,应使用parent映射表而非遍历 current = None; // 简化处理 } path.reverse(); Some(path)} 让我们创建一个简单的网格地图并运行A*搜索:
fn main() { // 创建5x5网格,true=可通过,false=障碍 let grid = vec![ vec![true, true, true, false, true], vec![true, false, true, false, true], vec![true, false, true, true, true], vec![true, true, true, false, true], vec![false, true, true, true, true], ]; let start = Point { x: 0, y: 0 }; let end = Point { x: 4, y: 4 }; match astar_search(&grid, start, end) { Some(path) => { println!("找到路径!路径长度: {}", path.len()); for (i, p) in path.iter().enumerate() { println!("步骤 {}: ({}, {})", i, p.x, p.y); } } None => println!("未找到可行路径!"), }} 恭喜!你已经用Rust成功实现了A*路径搜索算法。这个实现虽然基础,但涵盖了所有核心概念。在实际项目中(如Rust游戏开发),你可以进一步优化:
HashMap存储节点以提高查找效率通过本教程,你不仅掌握了Rust A*算法的实现,还深入理解了路径规划的核心思想。无论是做Rust算法教程学习,还是进行实际Rust游戏开发,这都是宝贵的基础!
提示:完整代码可在GitHub上找到。尝试修改网格或启发函数,观察路径变化!
本文由主机测评网于2025-12-11发表在主机测评网_免费VPS_免费云服务器_免费独立服务器,如有疑问,请联系我们。
本文链接:https://www.vpshk.cn/2025125953.html