A*算法的认识与求第K短路模板
现在来解决A*求K短路问题
在一个有权图中,从起点到终点最短的路径成为最短路,第2短的路成为次短路,第3短的路成为第3短路,依此类推,第k短的路成为第k短路。那么,第k短路怎么求呢?
对于第k短路,可以想到的一个比较朴素的算法就是广度优先搜索,使用优先队列从源点s进行广搜,当第k次搜索到终点t时,所的长度即所求但是这种方法在运行过程中会产生特别多的状态,当图比较简单、k比较小时,可以一试,但是当k较大或者图中点数较多时,会面临爆栈的危险。目前使用比较多的算法是单源最短路配合A*。A*是搜索中比较高级的方式,A*算法结合了启发式方法(这种方法通过充分利用图给出的信息来动态的作出决定而使搜索次数大大降低)和形式化方法(这种方法不利用图给出的信息,而仅通过数学的形式分析,如Dijkstra算法)。它通过一个估价函数f(h)来估计图中的当前点p到终点的距离,并由此决定它的搜索方向,当这条路径失败时,它会尝试其他路径。对于A*,估价函数=当前值+当前位置到终点的距离,即f(p)=g(p)+h(p),每次扩展估价函数值最小的一个。对于第k短路算法来说,g(p)为从源点s到当前点p所走的路径长度,h(p)为从当前点p到终点t的最短路,因此f(p)的意义就是从s按照当前路径经过p点后到达t的总距离。也就是每次扩展都是有方向的,这样无论对提高出解的速度还是降低扩展的状态数目都是有好处的。为了加快计算,h(p)需要在搜索之前进行预处理,只要将原图的所有边反向,再从终点t做一次单源最短路即可得到h(p)。单源最短路求法有Dijkstra,Bellman-Ford,SPFA等。
具体步奏:
这里我们使用链式前向星来存储如图,由于需要预处理所有点到终点的最短路,就需要将图G中所有边反向得到图G',再从终点t做一次单源最短路,所以实际上就是两张图。
(1)将有向图的所有边反向(无向图可以省略此步),以原图终点t为源点做一次单源最短路,结果记入数组dis[i]中,dis[i]即为原图中点i到点t的最短距离。这里的dis[i]即上述的h(p);
(2)新建一个优先队列,将源点s加入到队列中;
(3)从优先队列中弹出f(p)最小的点p(这里如果存在f(p)相等的点,则弹出g(p)最小的点),如果点p就是终点t,则计算t出队列的次数,如果当前为t的第k次出队,则当前路径长度就是s到t的第k短路,算法结束;否则遍历与p相连的所有的边,将扩展出的到p的邻接点信息加入到优先队列。
值得注意的是,当s==t时需要计算(k+1)短路,因为s到t这条距离为0的路不能算在这k短路中,这时只需将k自增1后再求第k短路即可。
现在来实战下:
POJ 2449 Remmarguts' Date ( 第 k 短路 && A*算法 )
题意 : 给出一个有向图、求起点 s 到终点 t 的第 k 短路、不存在则输出 -1
#include<stdio.h> #include<string.h> #include<queue> #include<algorithm> using namespace std; const int INF = 0x3f3f3f3f; const int maxn = 1024; const int maxm = 100008; struct EDGE{ int v, nxt, w; }; struct NODE{ int pos, Cost, F; bool operator < (const NODE & rhs) const {//重载的时候注意符号 if(this->F == rhs.F) return this->Cost > rhs.Cost; return this->F > rhs.F; }; }; EDGE Edge[maxm]; EDGE REdge[maxm]; int Head[maxn], RHead[maxn]; int cnt, Rcnt; int N; void init() { memset(Head, -1, sizeof(Head)); memset(RHead, -1, sizeof(RHead)); cnt = Rcnt = 0; } void AddEdge(int from, int to, int weight) { Edge[cnt].w = weight; Edge[cnt].v = to; Edge[cnt].nxt = Head[from]; Head[from] = cnt++; } void AddREdge(int from, int to, int weight) { REdge[Rcnt].w = weight; REdge[Rcnt].v = to; REdge[Rcnt].nxt = RHead[from]; RHead[from] = Rcnt++; } int vis[maxn]; int H[maxn]; void SPFA(int st) { queue<int> que; memset(H, INF, sizeof(H)); memset(vis, 0, sizeof(vis)); H[st] = 0; que.push(st); while(!que.empty()){ int cur = que.front(); que.pop(); vis[cur] = 0; for(int i=RHead[cur]; i!=-1; i=REdge[i].nxt) { int v = REdge[i].v; if(H[v] > H[cur] + REdge[i].w) { H[v] = H[cur] + REdge[i].w; if(!vis[v]) { vis[v] = 1; que.push(v); } } } } } int A_Star(int s, int t, int k) { if(s == t) k++; if(H[s]==INF) return -1; priority_queue<NODE> que; NODE cur, into; cur.pos = s; cur.Cost = 0; cur.F = H[s]; que.push(cur); int CNT = 0; while(!que.empty()){ cur = que.top(); que.pop(); if(cur.pos == t) CNT++; if(CNT == k) return cur.Cost; for(int i=Head[cur.pos]; i!=-1; i=Edge[i].nxt){ into.Cost = cur.Cost+Edge[i].w; into.F = cur.Cost+Edge[i].w+H[Edge[i].v]; into.pos = Edge[i].v; que.push(into); } }return -1; } int main(void) { int M, K, S, des; while(~scanf("%d %d", &N, &M)){ init(); int from, to, weight; while(M--){ scanf("%d %d %d",&from, &to, &weight); AddEdge(from, to, weight); AddREdge(to, from, weight);//建反向边 } scanf("%d %d %d", &S, &des, &K); SPFA(des);//求其他点到终点的最短路,作为H值 printf("%d\n", A_Star(S,des,K)); } return 0; }