Spfa 求最短路

SPFA 求解最短路代码

c++

/*
 * Spfa 最短路算法:Shortest Path Faster Algorithm
 * 前言:
 *      先前,我们介绍了基于点的 Dijkstra 最短路 和 基于边的 Bellman-Ford算法,他们各有各的长处。这次我们介绍一下应用也非常广泛的 spfa 算法。
 *      spfa 是受到 Bellman-Ford算法的启发所产生的,并和基于点的最短路的算法有些相似。
 *      因为 Bellman-Ford 是完全遍历边,大多数边对于 dist 的更新毫无作用,白白消耗了算力。因此,我们能不能将更新的 dist 点加入到队列中,
 *      而后只是遍历更新的点,用其的边更新其他点。
 * 算法:
 *      首先规定源点src,终点为 tgt。初始化 dist[src] = 0,并将 src 放入队列 que 中,标记 st[src]= true,表示其在队列中。
 *      当队列不为空时:
 *          取出队列头 u,并将 st[u] = false,更新 u->v 的 dist,倘若 v 点被更新,我们将其加入到队列中(前提是他之前不在队列中)
 *      复杂度:
 *          O(NM)。说实话这个复杂度还是挺难想的。
 *          Dijkstra最短路 朴素方法o(N^2)可以理解。使用堆优化之后,因为堆的大小最大为 M,复杂度为 O(M log M) ,稍稍有些难。比如说为什么堆大小最大为 M。
 *          Bellman-ford 算法复杂度 O(NM),这是因为他最多遍历 N - 1 次边的集合。
 *          那么 spfa 算法 O(NM) 是怎么得到的。说实话挺难想的,我觉着因为他是从 Bellman-ford算法优化而来的,因此根据 Bellman-ford最对比,更容易理解。
 *          中心思想,我们找到距离 src 最先发现的近点,次先发现的近点,...,最后发现的近点的距离。我们不妨用 1st, 2ed, 3rd,...i th, (i + 1) th, n th表示。
 *              解释什么是最发现近点。最先发现近点是 dist 最先确定,且以后不会更新的点。他和最近点不同。因为题目中边权可能为负数。
 *              举个例子,原点为 1, 图中有边 1->2 权值为3, 1->3 权值为4, 1->4 权值为10, 4->2权值为-10。
 *              最近点是 2点,走的路径是 1->4->2,也就是说,然而2点距离1点最近,但是它的路径长度反倒更长。应用 Bellman-ford的思想,他反倒是 k=2时才可以找到。
 *              最先发现近点,次先发现近点是按照 Bellman-Ford的顺序来的。这是因为我们需要参照 Bellman-ford 分析其复杂度。
 *          
 *          第一发现近点,遇到的概率为 O(M),src 其实向外扩散一圈就可以知道了。我们假设,每次 O(M) 向外扩充的话,最后就是 O(NM)。
 *          
 *          或者是按照第二个思路来想,路径 src-> tgt 为 path = src -> node 1 -> node 2 -> node3 -> ... -> node i -> node (i+1) -> ... -> tgt
 *              src -> node 1需要 O(N), node i -> node (i + 1) -> 这是需要更新将 node i 上相临边其他更新的点都更新一遍,也是 O(M) (想想BFS情景即可)。
 *              其实这个想法,也是 Bellman-ford的证明思路。每一次遍历所有边,仅仅是 node i -> node (i + 1)。
 *          
 *      其实仔细一想 Spfa 算法和 BFS 也比较像,因为他就是向外扩充加队列。不过队列中的数据因边长不确定,可能会来回遍历。
 *      BFS求解最短路要求边长一致,因为这样可要保证扩充的层次性。 SPFA 就不断增加 queue 来改进了这一点。
 * 优点:
 *      spfa 算法的平均速度可以比拟 dijkstra
 *      算法整个设计过程中无任何假设,负边是可以容忍的。(如果存在负环,就会死循环)
 * 缺点:
 *      虽然,最坏复杂度基本达不到,但是万一被卡,真的就 GG 了。
 *
 */

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <queue>
#include <vector>
#include <algorithm>

using namespace std;
const int N = 100010, INF = 0x3f3f3f3f;

int h[N], e[N], ne[N], w[N], idx;
int n, m;
bool st[N];
int dist[N];

void add(int a, int b, int c) {
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}

int spfa(int src, int tgt) {
    // initialize
    queue<int> que;
    memset(st, false, sizeof st);
    memset(dist, 0x3f, sizeof dist);
    dist[src] = 0;
    st[src] = true;
    que.push(src);

    int u, v;
    while (que.empty() == false) {
        u = que.front();    que.pop();
        st[u] = false;
        for (int i = h[u]; ~i; i = ne[i]) {
            v = e[i];
            if (dist[v] > dist[u] + w[i]) {
                dist[v]  = dist[u] + w[i];
                if (st[v] == false) {
                    st[v] = true;
                    que.push(v);
                }
            }
        }
    }

    return dist[tgt];
}

int main()
{
    // initialize
    memset(ne, -1, sizeof ne);
    memset(h, -1, sizeof h);
    idx = 0;

    // input
    scanf("%d%d", &n, &m);
    int a, b, c;
    while (m -- ) {
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c);
    }

    int res = spfa(1, n);
    if (res >= INF / 2) {
        printf("impossible");
    } else {
        printf("%d\n", res);
    }

    return 0;
}

python

posted @ 2022-06-19 19:24  lucky_light  阅读(44)  评论(0编辑  收藏  举报