启发式A*算法解决最短路径问题

968 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

A*算法:

在搜索的过程中,使用一个估价函数对当前点进行评估,找到最好的状态进行搜索,直到找到目标。 估价函数F(x)=G(x)+H(x),其中G(x)为实际代价,H(x)为启发函数,这就是启发式。并且启发函数要满足:H(x)<=H^^(x),H^^(x)为真实值,意思是启发函数的要求是必须小于等于真实值,启发函数的选取决定着A*算法的优劣,当启发值与真实值完全相等时,此时会达到最优的情况。

A*算法解决最短路问题

A算法是一种启发式算法,在最短路径问题上是对Dijkstra算法的优化,加入启发函数提高搜索的效率。 而本文当中使用A算法解决最短路径问题采用的启发函数是该点距离终点的最短距离,而计算该点距离终点的最短距离的方法是使用dijksta算法从终点作为起点跑一遍。事实上,使用dijkstra求得的启发函数就是真实值,也就是说这种情况搜索效率最高,也就退回了线性结构,从路径上看就是一条直线,类似DFS的轨迹。

  • 代码如下:
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
typedef pair<int, PII> PIII;
const int N = 1010, M = 200010;

int n, m, S, T;
int h[N], rh[N], e[M], w[M], ne[M], idx;
int dist[N];
bool st[N];

//邻接表
void add(int h[],int a,int b,int c)
{
    e[idx] = b;
    w[idx] = c;
    ne[idx] = h[a];
    h[a] = idx++;
}

//计算终点T到各点的最短距离作为启发函数
void dijkstra()
{
    priority_queue<PII,vector<PII>,greater<PII>> heap;
    heap.push({0,T});//从终点开始搜
    memset(dist, 0x3f, sizeof dist);
    dist[T] = 0;

    while(heap.size())
    {
        auto t = heap.top();
        heap.pop();

        int ver = t.y;
        if(st[ver]) continue;
        st[ver] = true;

        for(int i=rh[ver];i!=-1;i=ne[i])
        {
            int j = e[i];
            if(dist[j]>dist[ver]+w[i])
            {
                dist[j] = dist[ver] + w[i];
                heap.push({dist[j],j});
            }
        }
    }
}

int astar()
{
    priority_queue<PIII, vector<PIII>, greater<PIII>> heap;//小根堆
    // 谁的d[u]+f[u]更小 谁先出队列
    heap.push({dist[S], {0, S}});
    cout<<S;
    while(heap.size())
    {
        auto t = heap.top();
        heap.pop();
        int ver = t.y.y,distance = t.y.x;
        if(distance>0)
            cout<<"->"<<ver;
        if(ver==T){//搜到了
            return distance;
        }
        for(int i=h[ver];i!=-1;i=ne[i])
        {
            int j = e[i];
            // 按 真实值+估计值 = d[j]+f[j] = dist[S,t] + w[t][j] + dist[j,T] 堆排
            // 真实值 distance+w[i]
            heap.push({distance+w[i]+dist[j],{distance+w[i],j}});
        }
    }
    return -1;
}

int main()
{
    cin >> m >> n;
    memset(h,-1,sizeof h);
    memset(rh,-1,sizeof rh);
    for(int i=0;i<n;i++)
    {
        int a,b,c;
        cin >> a >> b >> c;
        add(h,a,b,c);
        add(rh,b,a,c);
    }
    cin >> S >> T;
   
    // 从各点到终点的最短路距离 作为估计函数f[u]
    dijkstra();
    int ans=astar();
    if(ans!=-1)
        printf("\n从顶点%d到顶点%d的最短路径长度为:%d\n",S,T,ans);
    else
        printf("\n从顶点%d到顶点%d不存在最短路径",S,T);
    return 0;
}

运行结果: 在这里插入图片描述

Dijkstra算法与A*算法比较

Dijkstra算法和A*算法都是最短路径问题的常用算法,下面就对这两种算法的特点进行一下比较。

  1. Dijkstra算法计算==源点到其他所有点==的最短路径长度,A*关注==点到点==的最短路径(包括具体路径)。
  2. Dijkstra算法建立在较为抽象的图论层面,A*算法可以更轻松地用在诸如游戏地图寻路中。
  3. Dijkstra算法的实质是==广度优先搜索==,是一种发散式的搜索,所以空间复杂度和时间复杂度都比较高。对路径上的当前点,A*算法不但记录其到源点的代价,还计算当前点到目标点的期望代价,是一种==启发式==算法,也可以认为是一种深度优先的算法(估价函数最优),也可以认为是一种BFS+DFS的结合(估计值<真实值)。
  4. 由第一点,当目标点很多时,A*算法会带入大量重复数据和复杂的估价函数,所以==如果不要求获得具体路径而需要比较路径长度==时,Dijkstra算法会成为更好的选择。