A*算法详解

发布时间:2023-05-21

一、什么是A*算法

A算法是一种启发式搜索算法,它在寻找从起点到终点的最短路径时极具有效性和实用性。它采用估价函数来计算从某个点到目标点的距离,进而选取优先级最高的点进行搜索。相较于其他算法,A算法拥有更高的搜索效率和准确性。 关于估价函数,需要注意的是,它并不一定要与实际的距离一致,而是根据当前状态下获取的信息进行推导,得出最为可能的结果。A*采用的是启发函数,有较强的启发作用,这是它优于其他算法的优点之一。

二、A*算法的原理

任何搜索算法都需要寻找一种方式来衡量每个节点的价值,然后以某种规则决定查找的先后顺序。A*算法通过综合考虑两个因素来计算每个节点的价值:从起点到当前节点的真实代价(g(x))和从当前节点到终点的估计代价(h(x))。 因此,对于一个节点x,它的总代价就是 f(x) = g(x) + h(x)。在这个算法中,我们希望找到代价最小的点来作为下一个搜索节点。

三、A*算法的实现步骤

1. 构建地图

首先,需要确定起点、终点以及地图的其他状态。地图的构建可以采用矩阵等方式表达。 例如,我们可以使用二维数组来表示地图,1表示可通行,0表示障碍物。

int[][] map = {
    {1, 1, 1, 1, 0},
    {1, 0, 1, 1, 0},
    {1, 1, 1, 1, 0},
    {0, 0, 0, 1, 0},
    {0, 0, 0, 1, 1}
};

2. 定义节点和构建搜索树

我们用结构体来定义一个节点,它包括横坐标x、纵坐标y、f(g+h),g和h。我们采用优先队列来存储搜索过程中的所有节点,节点从队首到队尾按照f值的大小递增排序。

deque<pair<int,int>> q; //定义队列
int vis[111][111] = {}; //记录某个点是否被搜索过
struct node{
    int x, y, f, g, h;
};

3. 搜索

使用A*算法实现搜索的主要流程: (1)将起点节点入队,并将f、g、h值初始化为0。 (2)重复以下操作,直到队列为空或找到了终点节点: (3)从队列中选取f值最小的节点,并将其出队。 (4)遍历该节点周围的节点,如果找到了终点,返回结果。 (5)如果当前节点未被访问过,更新其f、g、h值,并将其入队。 流程展示:

//起点入队
q.push_back([start_x,start_y,0,0,0]);
while(!q.empty()) {
    node a = q.front();
    q.pop_front();
    vis[a.x][a.y] = 1;
    if(a.x == end_x && a.y == end_y) return a.g;
    for(int i=0;i<4;i++){
        int x = a.x + dx[i], y = a.y + dy[i];  //dx dy 是四个方向
        if(x>=1 && x<=N && y>=1 && y<=M && !vis[x][y] && map[x][y]) {
            node b;
            b.x = x; b.y = y;
            b.g = a.g + 1;
            b.h = abs(x-end_x)+abs(y-end_y);
            b.f = b.g + b.h;
            q.push_back(b);
        }
    }
    sort(q.begin(),q.end(),cmp);
}
return -1;//没有路径

其中f值的大小可以通过改变启发函数h(x)的取值来实现A*算法的不同策略,例如如果h(x)始终为0,则等同于广度优先搜索。

4. 代码示例

下面是A*算法的完整代码,其中有详细的注释信息。请注意对于实际问题,需要根据具体场景修改代码。

const int dx[4] = {0,-1,0,1}, dy[4] = {1,0,-1,0}; //四个方向
int N = 5, M = 5;  //地图大小
int vis[111][111] = {}; //记录某个点是否被搜索过
int map[111][111] = {};  //存储地图信息
struct node{
    int x, y, f, g, h;
};
bool cmp(const node &a, const node &b){
    return a.f > b.f;
}
int A_star(int start_x, int start_y, int end_x, int end_y){
    deque<node> q;  //定义队列 
    //起点入队
    q.push_back((node){start_x,start_y,0,0,0});
    while(!q.empty()) {
        node a = q.front();
        q.pop_front();
        vis[a.x][a.y] = 1; //标记该点已被搜索过
        if(a.x == end_x && a.y == end_y) return a.g; //找到了终点
        for(int i=0;i<4;i++){ //向四周遍历子节点
            int x = a.x + dx[i], y = a.y + dy[i];
            if(x>=1 && x<=N && y>=1 && y<=M && !vis[x][y] && map[x][y]) { //如果未访问且可通过
                node b;
                b.x = x; b.y = y;
                b.g = a.g + 1;
                b.h = abs(x-end_x)+abs(y-end_y); //曼哈顿距离
                b.f = b.g + b.h;
                q.push_back(b);
            }
        }
        sort(q.begin(),q.end(),cmp); //节点按照f值排序,保证每次搜索最优的点
    }
    return -1; //没有路径
}
int main(){
    //地图的初始化
    int map_array[5][5] = {
        {1, 1, 1, 1, 0},
        {1, 0, 1, 1, 0},
        {1, 1, 1, 1, 0},
        {0, 0, 0, 1, 0},
        {0, 0, 0, 1, 1}
    };
    for(int i=0;i<N;++i){
        for(int j=0;j<M;++j){
            map[i+1][j+1] = map_array[i][j];
        }
    }
    //寻找最短路径
    int step = A_star(1,1,5,5);
    cout<<step<<endl;
    return 0;
}