很多时候,给定的图存在负权边,这时类似Dijkstra算法(0(V^2),在稠密图上有优势)便没有了用武之地,而bellman_ford的复杂度又过高,SPFA算法便派上用场了。
算法核心思想:理解这个算法,最好先看看Bellman-Ford,因为他是对Bellman-Ford的一个优化,SPFA算法采用了一个队列,优化算法,用数组dict[]记录每个结点的最短路径估计值,而且用邻接表来存储图G。我们采取的方法是动态逼近法:设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。
需要注意的是:仅当图不存在负权回路时,SPFA能正常工作。如果图存在负权回路,由于负权回路上的顶点无法收敛,总有顶点在入队和出队往返,队列无法为空,这种情况下SPFA无法正常结束。
1 记录每个结点进队次数,超过|V|次表示有负权
2 记录这个结点在路径中处于的位置,ord[i],每次更新的时候ord[i]=ord[x]+1,若超过|V|则表示有负圈....
下面举一个实例来说明SFFA算法是怎样进行的:
={<V0,V1>,<V0,V4>,<V1,V2>,<V1,V4>,<V2,V3>,<V3,V4>,<V4,V2>} = {2,10,3,7,4,5,6},见下图:
算法执行时各步的Queue队的值和D数组的值由下表所示:
算法执行到第五步后,队Queue空,算法结束。源点V0到V1的最短路径为2,到V2的最短路径为5,到V3的最短路径为9,到V4的最短路径为9,结果显然是正确的。
SPFA算法模板1:(邻接矩阵) const int nMax = 1000; // 顶点的数量上限。 const int eMax = 2000; // 边的数量上限。 const int inf = 0xffffff; int n, edge[nMax][nMax], dict[nMax]; int queue[eMax]; // 创建新队列,也可以用STL中的#include<queue>,但效率较低。 bool vis[nMax]; void init_data(s){ for(i = 1; i <= n; i ++) // 初始化dict[]。 dict[i] = inf; dict[s] = 0; // 起点s。 } void spfa(int s){ // 初始结点s,即为起点,若目标结点t,则输出dict[t]。 init_data(s); int head = 0, tail = 1; // int path[Max]; // 可增加一个path[]数组来记录最后s到t的路径。 queue[0] = x; dict[s] = 0; while(tail > head){ int u = queue[head]; vis[u] = true; for(i = 1; i <= n; i ++){ if(dict[i] > dict[u] + edge[u][i]){ dict[i] = dict[u] + edge[u][i]; // path[i] = u; if(!vis[i]){ // 对以前没有访问过的顶点,加入队列中。 vis[i] = true; queue[tail] = i; tail ++; } } } vis[u] = false; // 记得把出队列的顶点的vis[]置为false。 head ++; } } SPFA算法模板2:(邻接表) #include<iostream> using namespace std; #define MAXE 10000 #define MAXV 1000 int inf = 1000000000; struct edge_t { int s, t; int w; int next; }; edge_t arrEdge[MAXE]; //arrEdge[1...szEdge]保存szEdge条边. int szEdge; int arrHead[MAXV]; int nv; int arrPre[MAXV]; int arrDis[MAXV]; int Que[MAXE*10]; //队列 char inQue[MAXV]; //点v是否在队列中 void SPFA(int s) { int i, ie, v, head, tail; edge_t e; for(i = 1; i <= nv; i++) arrDis[i] = inf, arrPre[i] = i; arrDis[s] = 0; arrPre[s] = -1; //初始化队列 memset(Que, 0, sizeof(Que)); memset(inQue, 0, sizeof(inQue)); head = 0, tail = 1; Que[head] = s; inQue[s] = 1; while(head < tail) { v = Que[head++]; inQue[v] = 0; for(ie = arrHead[v]; ie!=-1; ie = arrEdge[ie].next) { e = arrEdge[ie]; if(arrDis[e.t] > arrDis[v] + e.w) { arrDis[e.t] = arrDis[v] + e.w, arrPre[e.t] = v; if(!inQue[e.t]) { inQue[e.t] = 1; Que[tail++] = e.t; } } } } return ; } int main() { int ne, i, s, t, w, p; memset(arrHead, -1, sizeof(arrHead)); memset(arrEdge, 0, sizeof(arrEdge)); szEdge = 0; scanf("%d %d", &nv, &ne); for(i = 1; i <= ne; i++) { scanf("%d %d %d", &s, &t, &w); szEdge++; arrEdge[szEdge].s = s; arrEdge[szEdge].t = t; arrEdge[szEdge].w = w; arrEdge[szEdge].next = arrHead[s]; arrHead[s] = szEdge; szEdge++; arrEdge[szEdge].s = t; arrEdge[szEdge].t = s; arrEdge[szEdge].w = w; arrEdge[szEdge].next = arrHead[t]; arrHead[t] = szEdge; } SPFA(1); for(i = 1; i <= nv; i++) { printf("Distance from 1 to %d is %d.\nPath: %d", i, arrDis[i], i); p = arrPre[i]; while(p != -1) { printf(" <-- %d", p); p = arrPre[p]; } printf("\n\n"); } return 0; }