适用范围:给定的图存在负权边,这时类似Dijkstra等算法便没有了用武之地,而Bellman-Ford算法的复杂度又过高,SPFA算法便派上用场了。 我们约定有向加权图G不存在负权回路,即最短路径一定存在。当然,我们可以在执行该算法前做一次拓扑排序,以判断是否存在负权回路,但这不是我们讨论的重点。
主要思路:现将图用邻接表edges[mxan]来存储,(当然邻接矩阵也可以),然后进行SPFA操作,用dis[maxm]数组来记录源点到其他点的最短路,用vis[maxm]数组来标记哪些点已经被走过,用que[maxm]队列来维护当前要待松弛的点,每次取队首进行操作,由队首扩展出的点依次加入队尾,如此反复,直到队列为空为止。
实现方法:
建立一个队列,初始时队列里只有起始点,再建立一个表格记录起始点到所有点的最短路径(该表格的初始值要赋为极大值,该点到他本身的路径赋为0)。然后执行松弛操作,用队列里有的点作为起始点去刷新到所有点的最短路,如果刷新成功且被刷新点不在队列中则把该点加入到队列最后。重复执行直到队列为空。
判断有无负环:
如果某个点进入队列的次数超过N次则存在负环(SPFA无法处理带负环的图)
下面贴代码:
#include <iostream>
using namespace std;
#define INF 0x7fffffff
const int maxn = 100005;
const int maxm = 5005<<1;
struct Data{
int x;
int value;
int next;
}edges[maxm];
int first[maxm], dis[maxm], que[maxm], vis[maxm];
int N, M;
void read_edges(){
cin >> N >> M;
for(int i = 1; i <= N; i++){
first[i] = -1;
vis[i] = 0;
dis[i] = INF;
}
for(int e = 1, u, v, w; e <= M; e++){
cin >> u >> v >> w;
edges[e].x = v;
edges[e].value = w;
edges[e].next = first[u];
first[u] = e;
}
}
void SPFA(){
int start = 1; //此个节点可以是任意节点,从题目中推出
vis[start] = 1;
dis[start] = 0;
int head = 0, tail = 1, cur;
que[tail] = start; //第一个节点插入到队尾
while(head != tail){
head = (head + 1) % 1000; //head向后扩展一位以进行下一个节点的操作
//%1000可能是个小优化,相当于是一个滚动队列
cur = que[head];
vis[cur] = 0; //首先标记当前节点是没有走过的
int k = first[cur]; //当前节点所对应的第一条边,可以扩展出其他的所有边
while(k != -1){
if(dis[cur] > dis[edges[k].x] + edges[k].value) //松弛操作
dis[cur] = dis[edges[k].x] + edges[k].value;
if(!vis[edges[k].x]){
vis[edges[k].x] = 1;
tail = (tail + 1) % 1000;
que[tail] = edges[k].x; //把当前节点插入队尾
}
k = edges[k].next; //扩展出其他的所有边
}
}
// cout << dis[N] << endl;
}
int main(){
read_edges();
SPFA();
return 0;
}