第K短路(A*(astar)算法)

给定一张N个点(编号1,2…N),M条边的有向图,求从起点S到终点T的第K短路的长度,路径允许重复经过点或边。

注意: 每条最短路中至少要包含一条边。

输入格式

第一行包含两个整数N和M。

接下来M行,每行包含三个整数A,B和L,表示点A与点B之间存在有向边,且边长为L。

最后一行包含三个整数S,T和K,分别表示起点S,终点T和第K短路。

输出格式

输出占一行,包含一个整数,表示第K短路的长度,如果第K短路不存在,则输出“-1”。

数据范围

1≤S,T≤N≤1000
0≤M≤105
1≤K≤1000
1≤L≤100

输入样例:

2 2
1 2 5
2 1 4
1 2 2

输出样例:

14

 思路:A*算法就是在bfs上加了一个启发函数,也就是一个距离的估值,从而使搜索更直接,更准确,避免了许多不必要的搜索,A*算法的一条定理,只要估计值f(s)<= 实际距离d(s),每次从优先队列中bfs之前取出的用来扩展的就是当前最小距离,因为最短距离一定<=真实距离,所以可以:

1、用终点T作为dijkstra的起点在反向图中跑一遍dijkstra算出每个点到终点的最短距离,作为那个点的估值。

2、用astar算法,然后根据估值排序(把估计值作为pair的first放到优先队列就可以实现按估值排序了),从S开始bfs,每次的取出的就是最短,第k次到达T点就是第K短路了。

完整代码:

#include <iostream>
#include <cstring>
#include <queue>
#define pll pair<int,int>
#define plll pair<int,pll>
#define x first
#define y second
using namespace std;
const int maxm=2e5+5,maxn=1e3+5;
int head[maxn],e[maxm],net[maxm],w[maxn],to[maxm],rhead[maxn];
int S,T,K;
int cnt,n,m,dis[maxn],vis[maxn],f[maxn];//f[i]记录到达编号为i的点估计的距离(本题取的最短距离),所以f=dis

void addEdge(int *head,int a,int b,int l)
{
    e[cnt]=b;
    net[cnt]=head[a];
    w[cnt]=l;
    head[a]=cnt++;
}

void dijkstra()
{
    memset(dis,0x3f,sizeof dis);
    memset(vis,0,sizeof vis);
    priority_queue<pll,vector<pll>,greater<pll> >heap;//小根堆(每个元素为pair类型,放在一个vector里)
    heap.push({0,T});//从终点开始往回扩展,由此计算出每个点距离T的最短距离作为估值f
    dis[T]=0;//dijkstra算法,起点的dis初始值为0!
    while(heap.size()){
        pll t=heap.top();
        heap.pop();
        int u=t.y;
        if(vis[u]) continue;
        vis[u]=1;
        for(int i=rhead[u];~i;i=net[i]){
            int v=e[i];
            if(dis[u]+w[i]<dis[v]){
                dis[v]=dis[u]+w[i];
                heap.push({dis[v],v});//由此计算出每个点v距离T的最短距离作为估值f
            }
        }
    }
}

int astar()
{
    memcpy(f,dis,sizeof dis);
    memset(vis,0,sizeof vis);
    priority_queue<plll,vector<plll>,greater<plll> >heap;//小根堆(每个元素为pair<int,pair>(即:pair<int,<int,int>>)类型,放在一个vector里)
    heap.push({f[S],{0,S}});//first存放当前点的估值f(估计距离),priority_queue按first这个估计值排序(从小到大),second.first为当前点的真实距离,second.second为当前点的编号
    while(heap.size()){
        plll t=heap.top();//按估计值排序后,每次取出当前加上估值最小的距离,根据A*算法的定理,这样就是从当前到最后的最短距离遍历的所有距离的最小值(一开始是最短,第二次为第二短...),然后进行扩展
        heap.pop();
        int u=t.y.y,d=t.y.x;
        vis[u]++;
        if(u==T&&vis[u]==K) return d;//当前是第K次到达T这个点,那当前距离就是到达T的第K短距离
        for(int i=head[u];~i;i=net[i]){
            int v=e[i];
            heap.push({d+w[i]+f[v],{d+w[i],v}});//每次扩展时,当前最短距离+边权+估值作为下一次的估值,当前真实距离d+w[i],当前点编号v
        }
    }
    return -1;
}

int main()
{
//    ios::sync_with_stdio(false);
//    cin.tie(0);
    memset(head,-1,sizeof head);
    memset(rhead,-1,sizeof rhead);
    scanf("%d%d",&n,&m);
    while(m--){
        int a,b,l;
        scanf("%d%d%d",&a,&b,&l);
        addEdge(head,a,b,l);
        addEdge(rhead,b,a,l);
    }
    scanf("%d%d%d",&S,&T,&K);
    if(S==T) K++;//当S==T时,最短是自己到自己的,但该方案没有边,这里不算做一条最短路,所以除去这条,下条最短算作第一短,应该是求k+1短
    dijkstra();
    int ans=astar();
    cout<<ans<<endl;
    return 0;
}
发布了176 篇原创文章 · 获赞 9 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Mr_Kingk/article/details/104642176
今日推荐