FA_规划和控制(PC)-A*(规划01)

FA:formulas and algorithm,PC:planning and control

一、A * 算法核心概念

A * 是一种启发式搜索算法,结合了 Dijkstra 算法(保证最优)和贪心算法(搜索高效)的优点,核心思想是:

  • 对每个待探索的节点,计算一个评估函数 f(n)f(n)f(n),以此决定搜索优先级;
  • 优先搜索f(n)f(n)f(n)最小的节点,最终以最小代价找到从起点到终点的最优路径。

1. 核心评估函数

f(n)=g(n)+h(n)f(n)=g(n)+h(n)f(n)=g(n)+h(n)

  • g(n)g(n)g(n):从起点 到当前节点nnn的实际代价(已走路径的真实长度);
  • h(n)h(n)h(n):从当前节点n到终点估计代价(启发函数,关键!);
  • f(n)f(n)f(n):从起点经过节点n到终点的总估计代价

2. 启发函数(h(n)h (n)h(n))的选择

启发函数的设计直接决定 A * 的性能:

表格

启发函数类型 公式(二维网络) 特点
曼哈顿距离 h(n)=xn−xgoal+yn−ygoalh(n)=x_n-x_{goal}+y_n-y_{goal}h(n)=xn−xgoal+yn−ygoal 适用于只能上下左右移动的网格(无对角线)
欧几里得距离 h(n)=(xn−xgoal2+(yn−ygoal)2)h(n)=\sqrt{(x_n-x_{goal}^2+(y_n−y_{goal})^2)}h(n)=(xn−xgoal2+(yn−ygoal)2) 适用于可任意方向移动的场景(如机器人路径规划)
切比雪夫距离 h(n)=max(xn−xgoal,yn−ygoal)h(n)=max(x_n-x_{goal},y_n-y_{goal})h(n)=max(xn−xgoal,yn−ygoal) 适用于可对角线移动的网格

关键规则

  • h(n)h(n)h(n)必须是可采纳的(admissible):即h(n)≤h(n)≤h(n)≤ 节点n到终点的真实代价,这样才能保证 A * 找到最优路径;
    若h(n)=0h(n)=0h(n)=0,A * 退化为 Dijkstra 算法(搜索全图,效率低);
    若h(n)h(n)h(n)过大(超过真实代价),A * 可能找不到最优路径,但搜索速度更快。

3. 核心数据结构

  • 开放列表(Open List):存储待探索的节点,按f(n)从小到大排序;
  • 关闭列表(Closed List):存储已探索完成的节点,避免重复搜索。

二、A * 算法的使用场景

A * 因 "最优 + 高效" 的特性,是路径规划领域的首选算法,主要应用场景:

  • 游戏开发:游戏角色 / NPC 的路径寻路(如《王者荣耀》《我的世界》的角色移动);
  • 移动机器人:AGV、扫地机器人、无人机的全局路径规划(已知环境地图);
  • 自动驾驶:高精度地图下的全局路径规划(如从 A 点到 B 点的最优道路选择);
  • 物流 / 仓储:无人叉车、分拣机器人的路径规划;
  • 地图导航:高德 / 百度地图的步行 / 驾车路径规划(底层核心算法之一)。

优势

  • 保证找到最优路径(当h(n)h(n)h(n)可采纳时);
  • 搜索效率远高于 Dijkstra 算法(启发函数减少无效搜索);
  • 逻辑清晰,易于实现和扩展。

局限性

  • 依赖完整的环境地图(无法处理未知环境);
  • 内存消耗较大(需存储开放 / 关闭列表);
  • 对动态障碍物适配性差(需重新规划)。

三、A * 算法的具体操作步骤

以二维平面(连续空间)的机器人路径规划为例,核心步骤如下:

步骤 1:初始化

  • 定义起点SSS、终点GGG、障碍物区域;
  • 初始化开放列表(仅加入起点SSS,计算f(S)=g(S)+h(S)f(S)=g(S)+h(S)f(S)=g(S)+h(S),其中g(S)=0g(S)=0g(S)=0);
  • 初始化关闭列表(空);
  • 为每个节点记录父节点(用于回溯路径)。

步骤 2:循环搜索

  • 从开放列表中取出f(n)最小的节点n(当前节点);
  • 将节点n从开放列表移至关闭列表;
  • 若节点n是终点G,回溯父节点得到完整路径,结束算法;
  • 生成节点n的相邻节点(如上下左右 / 周围采样点);
  • 对每个相邻节点m:
    • 若m在障碍物区域 / 关闭列表中,跳过;
    • 计算g(m)=g(n)+节点n到mg(m) = g(n) +节点n到mg(m)=g(n)+节点n到m的实际距离;
    • 若m不在开放列表中:
      • 计算h(m)h(m)h(m)(如欧几里得距离),f(m)=g(m)+h(m)f(m)=g(m)+h(m)f(m)=g(m)+h(m);
      • 将m加入开放列表,记录父节点为n;
        若m已在开放列表中:
      • 若新的g(m)更小(说明有更优路径到达m),更新g(m)、f(m),并将m的父节点改为n;
  • 重复步骤 2,直到开放列表为空(无路径)或找到终点。

步骤 3:路径回溯

从终点GGG开始,依次回溯父节点,直到起点SSS,反转后得到完整路径。

四、A * 算法代码实现(二维连续空间)

以下是适配机器人路径规划的 A * 代码(连续空间,非网格),包含可视化功能,可直接运行:

bash 复制代码
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
import math

# 定义节点类(存储位置、代价、父节点)
class Node:
    def __init__(self, x, y):
        self.x = x          # 节点x坐标
        self.y = y          # 节点y坐标
        self.g = 0.0        # 起点到当前节点的实际代价
        self.h = 0.0        # 当前节点到终点的估计代价
        self.f = 0.0        # 总代价 f = g + h
        self.parent = None  # 父节点(用于回溯路径)

    def __eq__(self, other):
        # 重载相等判断,用于检查节点是否已存在
        if isinstance(other, Node):
            return (self.x == other.x) and (self.y == other.y)
        return False

# 定义A*规划器类
class AStarPlanner:
    def __init__(self):
        self.step_size = 0.5  # 相邻节点的步长(控制搜索精度)
        self.obstacle_radius = 1.0  # 障碍物影响半径
        self.max_iter = 5000  # 最大迭代次数(防止死循环)

    # 启发函数:欧几里得距离(可采纳,保证最优)
    def heuristic(self, node, goal_node):
        return math.hypot(node.x - goal_node.x, node.y - goal_node.y)

    # 检查节点是否在障碍物区域内
    def is_collision(self, node, obstacles):
        for (ox, oy) in obstacles:
            if math.hypot(node.x - ox, node.y - oy) < self.obstacle_radius:
                return True
        return False

    # 生成当前节点的相邻节点(8方向采样)
    def generate_neighbors(self, current_node):
        neighbors = []
        # 8个方向的偏移量(上下左右+对角线)
        directions = [
            (self.step_size, 0), (-self.step_size, 0),
            (0, self.step_size), (0, -self.step_size),
            (self.step_size, self.step_size), (-self.step_size, -self.step_size),
            (self.step_size, -self.step_size), (-self.step_size, self.step_size)
        ]
        for dx, dy in directions:
            x = current_node.x + dx
            y = current_node.y + dy
            # 限制坐标范围(可选,防止节点超出地图)
            if 0 <= x <= 10 and 0 <= y <= 10:
                neighbors.append(Node(x, y))
        return neighbors

    # 从开放列表中找到f值最小的节点
    def find_min_f_node(self, open_list):
        min_node = open_list[0]
        for node in open_list:
            if node.f < min_node.f:
                min_node = node
        return min_node

    # 规划路径
    def plan(self, start_pos, goal_pos, obstacles):
        # 初始化起点和终点节点
        start_node = Node(start_pos[0], start_pos[1])
        goal_node = Node(goal_pos[0], goal_pos[1])

        # 初始化开放列表和关闭列表
        open_list = [start_node]
        closed_list = []

        iter_count = 0
        while open_list and iter_count < self.max_iter:
            iter_count += 1

            # 步骤1:取出f值最小的节点
            current_node = self.find_min_f_node(open_list)
            open_list.remove(current_node)
            closed_list.append(current_node)

            # 步骤2:检查是否到达终点
            if current_node == goal_node:
                # 回溯路径
                path = []
                while current_node is not None:
                    path.append((current_node.x, current_node.y))
                    current_node = current_node.parent
                # 反转路径(从起点到终点)
                return path[::-1]

            # 步骤3:生成相邻节点
            neighbors = self.generate_neighbors(current_node)
            for neighbor in neighbors:
                # 跳过碰撞节点和已探索节点
                if self.is_collision(neighbor, obstacles) or neighbor in closed_list:
                    continue

                # 计算g值(当前节点到相邻节点的实际距离)
                tentative_g = current_node.g + math.hypot(
                    neighbor.x - current_node.x, neighbor.y - current_node.y
                )

                # 情况1:相邻节点不在开放列表中
                if neighbor not in open_list:
                    neighbor.g = tentative_g
                    neighbor.h = self.heuristic(neighbor, goal_node)
                    neighbor.f = neighbor.g + neighbor.h
                    neighbor.parent = current_node
                    open_list.append(neighbor)
                # 情况2:相邻节点已在开放列表中,检查是否有更优路径
                else:
                    # 找到开放列表中的对应节点
                    for open_node in open_list:
                        if open_node == neighbor:
                            if tentative_g < open_node.g:
                                open_node.g = tentative_g
                                open_node.f = open_node.g + open_node.h
                                open_node.parent = current_node
                            break

        # 若迭代结束仍未找到路径
        print("警告:未找到有效路径!")
        return []

# -------------------------- 测试代码 --------------------------
if __name__ == "__main__":
    # 1. 初始化A*规划器
    a_star = AStarPlanner()

    # 2. 设置起点、终点、障碍物
    start = (0.0, 0.0)       # 起点
    goal = (8.0, 8.0)        # 终点
    obstacles = [            # 障碍物列表
        (3.0, 3.0),
        (5.0, 5.0),
        (7.0, 3.0)
    ]

    # 3. 规划路径
    path = a_star.plan(start, goal, obstacles)

    # 4. 可视化结果
    plt.figure(figsize=(8, 8))
    # 绘制障碍物
    for (ox, oy) in obstacles:
        circle = Circle((ox, oy), a_star.obstacle_radius, color='r', alpha=0.3, label='Obstacle' if (ox, oy) == obstacles[0] else "")
        plt.gca().add_patch(circle)
    # 绘制路径
    if path:
        path_x = [p[0] for p in path]
        path_y = [p[1] for p in path]
        plt.plot(path_x, path_y, 'b-', linewidth=2, label='A* Path')
    # 绘制起点和终点
    plt.scatter(start[0], start[1], color='green', s=100, label='Start')
    plt.scatter(goal[0], goal[1], color='red', s=100, label='Goal')
    # 图表设置
    plt.xlabel('X (m)')
    plt.ylabel('Y (m)')
    plt.title('A* Path Planning')
    plt.legend(loc='upper left')  # 图例放左上角
    plt.grid(True)
    plt.axis('equal')
    plt.xlim(0, 10)
    plt.ylim(0, 10)
    plt.show()

五、代码关键部分解释

1. 节点类(Node)

存储节点的核心信息:坐标(x,y)、代价(g/h/f)、父节点(用于路径回溯),重载__eq__方法方便判断节点是否重复。

2. 核心方法

  • heuristic:计算欧几里得距离作为启发函数(可采纳,保证最优);
  • is_collision:检查节点是否在障碍物范围内(避免碰撞);
  • generate_neighbors:生成当前节点的 8 方向相邻节点(控制搜索范围);
  • find_min_f_node:从开放列表中找到f(n)最小的节点(核心搜索逻辑);
  • plan:主规划函数,实现 A * 的完整搜索流程。

3. 运行前置条件

安装依赖:

bash 复制代码
pip install numpy matplotlib

4. 运行效果

  • 代码会绘制出从(0,0)到(8,8)的最优路径,绕过(3,3)、(5,5)、(7,3)三个障碍物;
  • 路径是连续的坐标点,可直接用于机器人的运动控制;
  • 图例默认放在左上角(符合你之前的要求)。

六、A* vs APF(人工势场法)

表格

特性 A * 算法 APF 算法
路径最优性 保证最优(h (n) 可采纳时) 不一定最优,易陷入局部最小值
环境要求 需完整地图(全局规划) 可局部感知(局部规划)
实时性 中等(需搜索) 高(实时计算受力)
障碍物适配 静态障碍物最优 动态障碍物适配性好
实现复杂度 中等(需管理开放 / 关闭列表) 简单(仅计算受力)

七、结束语

  • 核心原理:A * 通过评估函数f(n)=g(n)+h(n)优先搜索最优路径,启发函数h(n)是关键(需可采纳以保证最优);
  • 关键步骤:初始化→循环搜索(取最小f节点→生成邻居→更新代价)→路径回溯;
  • 使用要点:
    • 启发函数优先选欧几里得 / 曼哈顿距离(保证可采纳);
    • 步长(step_size)越小,路径越精准,但搜索效率越低;
    • 适用于已知静态环境的全局最优路径规划;
  • 核心优势:相比 APF,A * 不会陷入局部最小值,能保证找到最优路径。
相关推荐
袋鼠云数栈UED团队1 小时前
基于 OpenSpec 实现规范驱动开发
前端·人工智能
Raink老师1 小时前
【AI面试临阵磨枪】什么是 Tokenization?子词分词(Subword)的优缺点?
人工智能·ai 面试
迷你可可小生2 小时前
面经(三)
人工智能·rnn·lstm
逻辑驱动的ken2 小时前
Java高频面试考点场景题09
java·开发语言·数据库·算法·oracle·哈希算法·散列表
云烟成雨TD2 小时前
Spring AI Alibaba 1.x 系列【28】Nacos Skill 管理中心功能说明
java·人工智能·spring
AI医影跨模态组学2 小时前
Cancer Letters(IF=10.1)中科院自动化研究所田捷等团队:整合纵向MRI与活检全切片图像用于乳腺癌新辅助治疗反应的早期预测及个体化管理
人工智能·深度学习·论文·医学·医学影像
oioihoii2 小时前
Graphify 简明指南
人工智能
数字供应链安全产品选型2 小时前
AI全生命周期安全:从开发到下线,悬镜安全灵境AIDR如何覆盖智能体每一个环节?
人工智能
2501_933329552 小时前
企业舆情处置实战:Infoseek数字公关AI中台技术架构与功能解析
大数据·人工智能·架构·数据库开发
帅小伙―苏2 小时前
力扣42接雨水
前端·算法·leetcode