A Star(A*)最优路径搜索算法简介

        A*(A-star)路径搜索算法是一种广泛应用于图搜索和路径规划中的启发式算法。它用于找到从起点到目标节点的最优路径,常用于机器人导航、游戏AI以及路径规划等领域。A算法结合了Dijkstra算法的优点和贪心最佳优先搜索的启发式方法,使其在效率和效果上表现优异。下面是对A算法的详细介绍:

一、算法原理

A*算法使用一个评价函数f(n)来指导搜索过程,其中f(n)是以下两个函数的和:

  • g(n): 从起点节点n的实际代价(通常是距离或消耗)。
  • h(n): 从节点n目标节点的估计代价(启发式函数)。
  • 因此,f(n) = g(n) + h(n)

        启发式函数h(n)是启发式估计,从当前节点到目标节点的代价。为了确保A*算法的最优性,启发式函数必须是可接受的(admissible),即它从不高估实际代价。常见的启发式函数包括:

  • 曼哈顿距离(用于格子地图)
  • 欧几里得距离
  • 对角线距离

二、算法步骤

  1. 初始化

    • 创建一个开放列表(open list),初始时仅包含起点节点。
    • 创建一个封闭列表(closed list),初始为空。
    • 设置起点节点的g值为0,h值为从起点到目标节点的启发式估计,f值g值 + h值
  2. 搜索循环

    • 从开放列表中取出f值最小的节点作为当前节点n
    • 如果当前节点是目标节点,则搜索结束,成功找到路径。
    • 否则,将当前节点n从开放列表移到封闭列表。
    • 对于当前节点n的每个相邻节点m
      • 如果相邻节点m在封闭列表中,跳过。
      • 如果相邻节点m不在开放列表中:
        • m添加到开放列表。
        • 计算mg值(从起点到m的实际代价)、h值(从m到目标节点的启发式估计)和f值
        • 设置相邻节点m的父节点为当前节点n
      • 如果相邻节点m已经在开放列表中,检查新的路径是否更优:
        • 如果新的路径g值更小,更新mg值f值和父节点。
  3. 路径重构

    • 如果找到目标节点,从目标节点开始,通过父节点逐个回溯到起点,得到完整路径。

三、C++代码

        代码是自己写了个初稿,借助ChatGPT优化后的,配置好VSCode环境可以直接跑。

Windows下的VSCode配置C++编译环境参考:Windows中VSCode配置编译C++环境与使用gdb调试-CSDN博客

#include <iostream>
#include <vector>
#include <queue>
#include <cmath>
#include <unordered_set>
#include <memory>

using namespace std;

const int MAP_W = 8;
const int MAP_H = 8;
const int WEIGHT_STRAIGHT = 10;
const int WEIGHT_DIAGONAL = 14;

const char STATE_NONE = '0';  // 未被访问的可达节点
const char STATE_BARRIER = '1';  // 障碍
const char STATE_OPEN = '2';  // 开放列表中
const char STATE_CLOSE = '3';  // 关闭列表中

vector<vector<char>> MAP(MAP_H, vector<char>(MAP_W, STATE_NONE));
vector<vector<char>> stateList = MAP;
vector<pair<int, int>> barrierList = {
    {2, 1}, {3, 1}, {4, 1}, {5, 1},
    {0, 3}, {1, 3}, {2, 3}, {3, 3},
    {3, 5}, {4, 5}, {5, 5}, {6, 5},
    {6, 0}, {6, 1}, {6, 2}, {6, 3}, {6, 4}
};

void init() {
    for (const auto& barrier : barrierList) {
        MAP[barrier.first][barrier.second] = STATE_BARRIER;
        stateList[barrier.first][barrier.second] = STATE_BARRIER;
    }
}

struct Node {
    int x, y;
    int g;  // 耗费值
    int f;  // 预测值
    shared_ptr<Node> parent;  // 父节点

    Node(int x, int y, int endX, int endY, int g, shared_ptr<Node> parent)
        : x(x), y(y), g(g), parent(parent) {
        int dx = abs(endX - x);
        int dy = abs(endY - y);
        f = g + (dx + dy) * WEIGHT_STRAIGHT;  // 曼哈顿距离
    }

    bool operator==(const Node& other) const {
        return x == other.x && y == other.y;
    }

    struct HashFunction {
        size_t operator()(const shared_ptr<Node>& node) const {
            return hash<int>()(node->x) ^ hash<int>()(node->y);
        }
    };
};

struct Compare {
    bool operator()(const shared_ptr<Node>& a, const shared_ptr<Node>& b) const {
        return a->f > b->f;
    }
};

priority_queue<shared_ptr<Node>, vector<shared_ptr<Node>>, Compare> openList;
unordered_set<shared_ptr<Node>, Node::HashFunction> closedList;

bool isValidPoint(int x, int y) {
    return x >= 0 && y >= 0 && x < MAP_W && y < MAP_H;
}

void openNode(shared_ptr<Node> current, int endX, int endY) {
    int directions[8][2] = {
        {-1, 0}, {1, 0}, {0, -1}, {0, 1},
        {1, 1}, {-1, 1}, {-1, -1}, {1, -1}
    };

    for (int i = 0; i < 8; ++i) {
        int newX = current->x + directions[i][0];
        int newY = current->y + directions[i][1];

        if (!isValidPoint(newX, newY)) 
			continue;

        int cost = (i < 4) ? WEIGHT_STRAIGHT : WEIGHT_DIAGONAL;

        if (stateList[newX][newY] == STATE_NONE || stateList[newX][newY] == STATE_OPEN) {
            auto newNode = make_shared<Node>(newX, newY, endX, endY, current->g + cost, current);
            openList.push(newNode);
            stateList[newX][newY] = STATE_OPEN;
        }
    }
}

vector<shared_ptr<Node>> searchPath(int startX, int startY, int endX, int endY) {
    vector<shared_ptr<Node>> path;
    auto startNode = make_shared<Node>(startX, startY, endX, endY, 0, nullptr);
    openList.push(startNode);
    closedList.insert(startNode);

    while (!openList.empty()) {
        auto current = openList.top();
        openList.pop();

        if (current->x == endX && current->y == endY) {
            while (current) {
                path.push_back(current);
                current = current->parent;
            }
            break;
        }

        stateList[current->x][current->y] = STATE_CLOSE;
        closedList.insert(current);
        openNode(current, endX, endY);
    }
    return path;
}

void printMap() {
    cout << "\nMAP:\n";
    for (const auto& row : MAP) {
        for (const auto& cell : row) {
            cout << cell;
        }
        cout << endl;
    }
}

void printStateList() {
    cout << "\nStateList:\n";
    for (const auto& row : stateList) {
        for (const auto& cell : row) {
            cout << cell;
        }
        cout << endl;
    }
}

int main() {
    int startX = 5, startY = 0;
    int endX = 7, endY = 1;
    init();
    vector<shared_ptr<Node>> path = searchPath(startX, startY, endX, endY);

    for (auto node : path) {
        MAP[node->x][node->y] = '#';
    }

    printMap();
    printStateList();

    if (path.empty()) {
        cout << "Unable to reach the target point!!!\n";
    } else {
        cout << "Path found!!!\n";
    }

    return 0;
}

 

四、优点与缺点

优点

  • 高效性:A*算法通过结合实际代价和启发式估计,能够快速找到最优路径。
  • 灵活性:适用于多种图结构和启发式函数。
  • 最优性:如果启发式函数是可接受的,A*算法保证找到最优路径。

缺点

  • 内存消耗:A*算法需要维护开放列表和封闭列表,可能占用大量内存,特别是在图非常大的情况下。
  • 计算量:在最坏情况下,A*算法可能需要遍历大量节点,计算复杂度较高。

五、应用场景

  • 机器人导航:机器人在地图中规划路径,避开障碍物到达目标位置。
  • 游戏AI:游戏中的角色在地图中寻找最短路径,例如从一个位置到另一个位置。
    • 路径规划:应用于物流、交通系统等领域,优化运输路径。

六、Dijkstra算法与A*算法对比

Dijkstra算法和A*算法都是用于图搜索和路径规划的经典算法。它们之间有一些相似之处,但也有显著的区别。以下是对这两种算法的比较:

1. 基本原理

Dijkstra算法

  • 原理:Dijkstra算法是一种无权图或正权图中的单源最短路径算法。它逐步扩展起点到所有其他节点的最短路径,直到找到目标节点或所有节点都已访问。
  • 成本函数:Dijkstra算法只考虑从起点到当前节点的实际代价g(n)
  • 特点:不需要启发式函数,因此适用于没有明确目标或所有目标具有相同优先级的情况。

A*算法

  • 原理:A*算法是在Dijkstra算法的基础上加入了启发式函数h(n),用于估计从当前节点到目标节点的代价。它结合了实际代价和启发式估计来指导搜索过程。
  • 成本函数:A*算法考虑从起点到当前节点的实际代价g(n)以及从当前节点到目标节点的启发式估计h(n),即f(n) = g(n) + h(n)
  • 特点:需要启发式函数,因此适用于有明确目标并且可以估计目标距离的情况。

2. 性能和效率

Dijkstra算法

  • 时间复杂度:O((V + E)logV),其中V是节点数,E是边数。在稠密图中表现较差。
  • 空间复杂度:O(V),需要存储所有节点的距离和访问状态。
  • 搜索范围:Dijkstra算法会探索所有可能的路径,直到找到目标节点的最短路径。因此,它在没有启发式信息的情况下可能会比较慢。

A*算法

  • 时间复杂度:取决于启发式函数的好坏。在最坏情况下,复杂度与Dijkstra算法相同,但在实际应用中通常比Dijkstra算法更快。
  • 空间复杂度:O(V),需要存储所有节点的距离、启发式估计和访问状态。
  • 搜索范围:A*算法利用启发式函数引导搜索,通常会探索较少的节点,特别是在启发式函数较好的情况下。

3. 应用场景

Dijkstra算法

  • 适用情况:适用于所有边权为非负的图,特别是没有明确目标或所有目标优先级相同的情况。
  • 常见应用:网络路由、交通规划、资源分配等。

A*算法

  • 适用情况:适用于需要寻找特定目标节点的最短路径,并且可以定义合理启发式函数的情况。
  • 常见应用:游戏AI路径规划、机器人导航、地图路径规划等。

4. 优缺点

Dijkstra算法

优点

  • 确保找到从起点到所有节点的最短路径。
  • 不需要启发式函数,适用范围广。

缺点

  • 对于大规模图搜索效率较低,因为它没有利用任何启发信息。
  • 在没有明确目标节点时,搜索范围可能非常大。

A*算法

优点

  • 通过启发式函数引导搜索,通常比Dijkstra算法更快。
  • 启发式函数设计合理时,能够高效找到最优路径。

缺点

  • 需要合理的启发式函数设计,不同问题需要不同的启发式函数。
  • 启发式函数不合理时,可能退化为Dijkstra算法,效率降低。

5. 示例对比

Dijkstra算法示例

        在一个简单的无权图中,Dijkstra算法会逐步扩展起点的所有邻居,直到找到目标节点。例如,在一个5x5的网格中从左上角到右下角的路径,Dijkstra算法会访问所有可能的路径,直到找到最短路径。

A*算法示例

        在同样的5x5网格中,A算法会根据启发式函数(例如曼哈顿距离)引导搜索过程,通常只会访问较少的节点。例如,如果起点在左上角,目标在右下角,A算法会优先考虑向右和向下的路径,因为这些路径在启发式估计中距离目标较近。

总结

  • Dijkstra算法:适用于所有边权为非负的图,适合没有明确目标或所有目标具有相同优先级的情况。它的搜索范围较大,但能够确保找到从起点到所有节点的最短路径。
  • A*算法:适用于需要寻找特定目标节点的最短路径,并且可以定义合理启发式函数的情况。它的搜索范围较小,效率更高,特别是在启发式函数设计合理的情况下。
  • 21
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值