路径搜索算法——A*算法

路径搜索算法——A*算法

一、A*算法简介

A* 是一种用于图搜索的启发式搜索算法(Heuristic Search Algorithm),常用于求解从起点到目标点的最短路径问题。它结合了 Dijkstra 算法的最短路径特性与贪心算法的目标导向性,既高效又能找到最优解

二、算法核心思想

每个节点的总评估代价:

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

  • f(n):从起点到终点经过当前点n的总代价估计; f(n):从起点到终点经过当前点 n 的总代价估计; f(n):从起点到终点经过当前点n的总代价估计;

  • g(n):从起点到当前点n的实际代价; g(n):从起点到当前点 n 的实际代价; g(n):从起点到当前点n的实际代价;

  • h(n):从当前点n到目标点的启发函数(Heuristicfunction),即代价估计。 h(n):从当前点 n 到目标点的启发函数(Heuristic function),即代价估计。 h(n):从当前点n到目标点的启发函数(Heuristicfunction),即代价估计。

在这里插入图片描述

代价函数分为两部分,一部分是到目前点的代价,另外一部分是通过启发函数h(n)h(n)h(n)计算到目标点的距离

三、启发函数 h(n)h(n)h(n)

启发函数是算法效率的关键。常用的启发函数包括:

启发函数适用场景说明
曼哈顿距离(Manhattan)仅水平垂直移动的栅格地图h(n)=∣x1−x2∣+∣y1−y2∣h(n) =∣x1−x2∣+∣y1−y2∣h(n)=∣x1x2+y1y2
欧几里得距离(Euclidean)可以斜着走的平面图h(n)=(x1−x2)2+(y1−y2)2h(n) = \sqrt{(x_1 - x_2)^2 + (y_1 - y_2)^2}h(n)=(x1x2)2+(y1y2)2
对角线距离允许八方向移动h(n)=max(∣x1−x2∣,∣y1−y2∣)h(n) = max(∣x1−x2∣,∣y1−y2∣)h(n)=max(x1x2,y1y2)

如果 h(n)h(n)h(n)永远不超过真实的最短代价,则称它是可接受的(admissible),A* 就能保证找到最优路径。

四、A* 算法流程(伪代码)

1. 将起点加入开放列表 open_list
2. 初始化关闭列表 closed_list 为空

3. 当 open_list 不为空时:
   a. 从 open_list 中选出 f 最小的节点 current
   b. 如果 current 是目标节点,则回溯返回路径
   c. 将 current 加入 closed_list
   d. 遍历 current 的所有相邻节点 neighbor:
       - 如果 neighbor 在 closed_list 中,则跳过
       - 计算 neighbor 的 g、h、f 值
       - 如果 neighbor 不在 open_list 中,则加入
       - 如果在 open_list 中,且新的 g 更小,则更新其 g 和父节点

五、Python 简化实现示例

以下为二维栅格地图上的 A* 实现(仅上下左右四邻接):

import heapq  # 导入堆队列模块,用作优先队列(open list)

def astar(grid, start, goal):
    # 获取地图大小
    rows, cols = len(grid), len(grid[0])

    # 启发函数:使用曼哈顿距离作为估算从当前位置到目标点的代价
    def h(n):
        return abs(n[0] - goal[0]) + abs(n[1] - goal[1])

    # open_list 使用最小堆结构,每个元素是 (f, g, 当前节点坐标, 当前路径)
    open_list = [(h(start), 0, start, [])]

    # visited 用于记录已经访问过的节点,避免重复访问
    visited = set()

    # A* 主循环,只要 open_list 非空就一直搜索
    while open_list:
        # 弹出 f 最小的节点(f = g + h)
        f, g, node, path = heapq.heappop(open_list)

        # 如果这个节点已经访问过,则跳过
        if node in visited:
            continue
        visited.add(node)

        # 将当前节点加入路径轨迹
        path = path + [node]

        # 如果当前节点是目标点,返回完整路径
        if node == goal:
            return path

        # 遍历当前节点的四个相邻方向(上、下、左、右)
        for dx, dy in [(-1,0), (1,0), (0,-1), (0,1)]:
            x, y = node[0] + dx, node[1] + dy

            # 判断是否越界,是否为障碍物(1表示障碍,0表示可通行)
            if 0 <= x < rows and 0 <= y < cols and grid[x][y] == 0:
                # 新的 g = 当前 g + 1(每步代价为 1)
                new_g = g + 1
                # 新的 f = g + h
                new_f = new_g + h((x, y))
                # 将该邻居节点加入 open_list,路径为当前路径加当前节点
                heapq.heappush(open_list, (new_f, new_g, (x, y), path))

    # 如果搜索结束都没找到目标,则返回 None
    return None

# 示例地图:0 为可通行区域,1 为障碍物
grid = [
    [0, 0, 0, 0],
    [1, 1, 0, 1],
    [0, 0, 0, 0],
    [0, 1, 1, 0],
    [0, 0, 0, 0]
]

# 定义起点和终点
start = (0, 0)
goal = (4, 3)

# 调用 A* 函数进行路径搜索
path = astar(grid, start, goal)

# 打印搜索结果
if path:
    print("找到路径:")
    print(path)
else:
    print("无可行路径")


六、C++ 简化实现示例

下面是一个用 C++ 实现 A* 算法 的简化示例,支持二维栅格地图(四方向移动),使用曼哈顿距离作为启发函数。

#include <iostream>
#include <vector>
#include <queue>
#include <cmath>
#include <unordered_set>
#include <unordered_map>
#include <algorithm>

using namespace std;

// 节点结构体,包含坐标、代价信息和父节点指针
struct Node {
    int x, y;           // 节点坐标
    int g, h;           // g: 起点到当前点的代价;h: 启发函数估值
    Node* parent;       // 父节点指针,用于路径回溯

    Node(int x_, int y_, int g_, int h_, Node* p = nullptr)
        : x(x_), y(y_), g(g_), h(h_), parent(p) {}

    int f() const { return g + h; }  // 总代价 f = g + h

    // 重载运算符,用于 priority_queue 比较,f 值小的优先
    bool operator>(const Node& other) const {
        return f() > other.f();
    }
};

// 哈希函数:允许 unordered_map/set 使用 pair<int, int> 作为 key
struct PairHash {
    size_t operator()(const pair<int, int>& p) const {
        return hash<int>()(p.first) ^ hash<int>()(p.second << 1);
    }
};

// 检查位置 (x, y) 是否在地图范围内且不是障碍物
bool isValid(int x, int y, const vector<vector<int>>& grid) {
    return x >= 0 && x < grid.size() && y >= 0 && y < grid[0].size() && grid[x][y] == 0;
}

// 启发函数:曼哈顿距离,用于估计当前点到目标点的代价
int manhattan(int x1, int y1, int x2, int y2) {
    return abs(x1 - x2) + abs(y1 - y2);
}

// 回溯路径:从终点向父节点递归,生成完整路径
vector<pair<int, int>> reconstructPath(Node* node) {
    vector<pair<int, int>> path;
    while (node) {
        path.emplace_back(node->x, node->y);
        node = node->parent;
    }
    reverse(path.begin(), path.end()); // 起点在前
    return path;
}

// A* 主算法函数
vector<pair<int, int>> astar(const vector<vector<int>>& grid,
                              pair<int, int> start,
                              pair<int, int> goal) {
    int m = grid.size(), n = grid[0].size();

    // open list:优先队列,按 f 值升序排列
    priority_queue<Node, vector<Node>, greater<Node>> open;

    // 记录所有创建过的节点,用于避免重复生成
    unordered_map<pair<int, int>, Node*, PairHash> allNodes;

    // 记录已访问节点(closed list)
    unordered_set<pair<int, int>, PairHash> closed;

    // 创建起点节点,g=0,h=曼哈顿距离
    Node* startNode = new Node(start.first, start.second,
                               0, manhattan(start.first, start.second, goal.first, goal.second));
    open.push(*startNode);
    allNodes[start] = startNode;

    // 允许移动的方向(上、下、左、右)
    vector<pair<int, int>> directions = {{-1,0}, {1,0}, {0,-1}, {0,1}};

    // 开始搜索
    while (!open.empty()) {
        Node current = open.top();
        open.pop();
        pair<int, int> currPos = {current.x, current.y};

        // 如果已经访问过该节点,则跳过
        if (closed.count(currPos)) continue;
        closed.insert(currPos);

        // 如果到达目标点,则回溯路径
        if (currPos == goal) {
            return reconstructPath(allNodes[currPos]);
        }

        // 遍历当前节点的相邻节点
        for (auto [dx, dy] : directions) {
            int nx = current.x + dx;
            int ny = current.y + dy;
            pair<int, int> neighbor = {nx, ny};

            // 如果不合法或已经访问过,跳过
            if (!isValid(nx, ny, grid) || closed.count(neighbor)) continue;

            // 计算新 g 和 h
            int g_new = current.g + 1; // 每移动一步代价为 1
            int h_new = manhattan(nx, ny, goal.first, goal.second);

            // 如果是新节点或找到更短路径,更新信息并加入 open list
            if (!allNodes.count(neighbor) || g_new < allNodes[neighbor]->g) {
                Node* nextNode = new Node(nx, ny, g_new, h_new, allNodes[currPos]);
                open.push(*nextNode);
                allNodes[neighbor] = nextNode;
            }
        }
    }

    // 未找到路径,返回空
    return {};
}

// 主函数:测试 A* 算法
int main() {
    // 地图:0 可通行,1 是障碍物
    vector<vector<int>> grid = {
        {0, 0, 0, 0},
        {1, 1, 0, 1},
        {0, 0, 0, 0},
        {0, 1, 1, 0},
        {0, 0, 0, 0}
    };

    pair<int, int> start = {0, 0};
    pair<int, int> goal = {4, 3};

    auto path = astar(grid, start, goal);

    // 打印路径结果
    if (path.empty()) {
        cout << "未找到可行路径。" << endl;
    } else {
        cout << "路径如下:" << endl;
        for (auto [x, y] : path) {
            cout << "(" << x << ", " << y << ") ";
        }
        cout << endl;
    }

    return 0;
}

七、优缺点分析

✅ 优点

  • 最优性保证:若启发函数是可接受的(不高估),一定返回最优路径;
  • 效率高:比 Dijkstra 更快,搜索空间更小;
  • 适用广泛:可用于地图路径规划、游戏AI、自动驾驶等。

❌ 缺点

  • 内存开销大:需要维护开放列表和闭合列表;
  • 对启发函数敏感:若设计不当,效率急剧下降;
  • 动态场景适应性差:适合静态环境,动态障碍或需频繁重规划时性能不佳。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值