[旧版][知识点]SPFA算法

// 此博文为迁移而来,写于2015年4月9日,不代表本人现在的观点与看法。原始地址:http://blog.sina.com.cn/s/blog_6022c4720102vx93.html

Update - 20200608

该博文内容现已更新并迁移至:

[知识点] 8.5 最短路 https://www.cnblogs.com/jinkun113/p/13063162.html

1、前言
       最短路算法有很多种,类似于Floyd和Dijkstra都是很早之前就学了的。其实每种最短路算法有各自的优势。Floyd适合于跑完全图,但是效率太慢(O(n3))。Dijkstra适合于跑没有负权的图,效率为O(n2)。而今天介绍的SPFA算法,是有一位中国人——段凡丁所提出来的(其实我很想吐个槽。。为什么人家弗洛伊德大叔提出了算法就叫弗洛伊德算法,迪杰斯特拉大爷提出的东西就叫迪杰斯特拉算法,段凡丁提出来的算法就变成了什么鬼SPFA(Shortest Path Faster Algorithm)。。严重的歧视啊。。。)
       其实SPFA没什么要讲的。看了一下模板就瞬间懂了。
       
2、概念
       SPFA算法,是队列实现的Bellman-Ford算法。非常好理解,都知道BFS跑无权迷宫吧?其实SPFA可以直接理解为BFS跑有权图。因为它的形式和BFS太像了。大致流程是用一个队列来进行维护。 初始时将源加入队列。 每次从队列中取出一个元素,并对所有与他相邻的点进行松弛(松弛的意思自行脑补吧,看了程序就懂了),若某个相邻的点松弛成功,则将其入队。 直到队列为空时算法结束。先上代码:
 
//代码更新于20160916
#include <cstdio>
#include <cstring>

#define MAXN 10000
#define MAXM 100000
#define INF 0x3f3f3f3f 

int T, n, m, u, v, w;
int h[MAXN], q[MAXN], o, dis[MAXN], vis[MAXN], s, t;

struct Edge {
    int v, next, w;
} edge[MAXM * 2];

void addEdge(int u, int v, int w) {
    edge[++o] = (Edge) {v, h[u], w}, h[u] = o;
}

int SPFA(int s, int t) {
    int head = 1, tail = 2;
    while (head != tail) {
        int o = q[head];
        for (int x = h[o]; x; x = edge[x].next) {
            int v = edge[x].v;
            if (dis[o] + edge[x].w < dis[v]) {
                dis[v] = dis[o] + edge[x].w;
                if (!vis[v]) q[tail++] = v, vis[v] = 1;
            }
        }
        vis[o] = 0;
        head++;
    }
    return dis[t];
}

int main() {
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= m; i++) {
        scanf("%d %d %d", &u, &v, &w);
        addEdge(u, v, w), addEdge(v, u, w);
    }
    scanf("%d", &T);
    for (int i = 1; i <= T; i++) {
        memset(vis, 0, sizeof(vis)), memset(dis, INF, sizeof(dis));
        scanf("%d %d", &s, &t);
        dis[s] = 0, vis[s] = 1, q[1] = s;
        printf("%d", SPFA(s, t));
    }
    return 0;
}
       初始化:设dis[i]代表目前源点到i点的最短距离,开始时dist全部为INF(无穷大),只有dis[s]=0。
       SPFA:维护一个队列,里面存放所有需要进行迭代的点。初始时队列中只有一个点s。用一个vis[i]记录i点是否处在队列中。每次迭代,取出队头的点v,依次枚举从v出发的边v->u,设边的长度为len,判断dis[v]+len是否小于dis[u],若小于则改进dis[u],并且由于s到u的最短距离变小了,有可能u可以改进其它的点,所以若u不在队列中,就将它放入队尾。这样一直迭代下去直到队列变空,也就是s到所有点的最短距离都确定下来,结束算法。
       备注:SPFA还有一个很强大的功能,可以判负权环。若一个点入队次数超过n,则有负权环。
原文地址:https://www.cnblogs.com/jinkun113/p/4678909.html