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