struct Edge { int u, v, w;};vector<Edge> edge;int dis[MAXN], u, v, w;constexpr int INF = 0x3f3f3f3f;bool bellmanford(int n, int s) { memset(dis, 0x3f, (n + 1) * sizeof(int)); dis[s] = 0; bool flag = false; // 判断一轮循环过程中是否发生松弛操作 for (int i = 1; i <= n; i++) { flag = false; for (int j = 0; j < edge.size(); j++) { u = edge[j].u, v = edge[j].v, w = edge[j].w; if (dis[u] == INF) continue; // 无穷大与常数加减仍然为无穷大 // 因此最短路长度为 INF 的点引出的边不可能发生松弛操作 if (dis[v] > dis[u] + w) { dis[v] = dis[u] + w; flag = true; } } // 没有可以松弛的边时就停止算法 if (!flag) { break; } } // 第 n 轮循环仍然可以松弛时说明 s 点可以抵达一个负环 return flag;}
Python
class Edge: def __init__(self, u=0, v=0, w=0): self.u = u self.v = v self.w = wINF = 0x3F3F3F3Fedge = []def bellmanford(n, s): dis = [INF] * (n + 1) dis[s] = 0 for i in range(1, n + 1): flag = False for e in edge: u, v, w = e.u, e.v, e.w if dis[u] == INF: continue # 无穷大与常数加减仍然为无穷大 # 因此最短路长度为 INF 的点引出的边不可能发生松弛操作 if dis[v] > dis[u] + w: dis[v] = dis[u] + w flag = True # 没有可以松弛的边时就停止算法 if not flag: break # 第 n 轮循环仍然可以松弛时说明 s 点可以抵达一个负环 return flag
队列优化:SPFA
即 Shortest Path Faster Algorithm。
很多时候我们并不需要那么多无用的松弛操作。
很显然,只有上一次被松弛的结点,所连接的边,才有可能引起下一次的松弛操作。
那么我们用队列来维护「哪些结点可能会引起松弛操作」,就能只访问必要的边了。
SPFA 也可以用于判断 s 点是否能抵达一个负环,只需记录最短路经过了多少条边,当经过了至少 n 条边时,说明 s 点可以抵达一个负环。
为什么 n 条边意味着负环?
一条不经过重复节点的路径,最多经过 n−1 条边(连接 n 个不同节点)
如果最短路需要 ≥n 条边,则路径上必有重复节点,即存在环
这个环能让路径变短(否则不会被松弛),所以是负环
实现
[list2tab]
C++
struct edge { int v, w;};vector<edge> e[MAXN];int dis[MAXN], cnt[MAXN], vis[MAXN];queue<int> q;bool spfa(int n, int s) { memset(dis, 0x3f, (n + 1) * sizeof(int)); dis[s] = 0, vis[s] = 1; q.push(s); while (!q.empty()) { int u = q.front(); q.pop(), vis[u] = 0; for (auto ed : e[u]) { int v = ed.v, w = ed.w; if (dis[v] > dis[u] + w) { dis[v] = dis[u] + w; cnt[v] = cnt[u] + 1; // 记录最短路经过的边数 if (cnt[v] >= n) return false; // 在不经过负环的情况下,最短路至多经过 n - 1 条边 // 因此如果经过了多于 n 条边,一定说明经过了负环 if (!vis[v]) q.push(v), vis[v] = 1; } } } return true;}
Python
from collections import dequeclass Edge: def __init__(self, v=0, w=0): self.v = v self.w = we = [[Edge() for i in range(MAXN)] for j in range(MAXN)]INF = 0x3F3F3F3Fdef spfa(n, s): dis = [INF] * (n + 1) cnt = [0] * (n + 1) vis = [False] * (n + 1) q = deque() dis[s] = 0 vis[s] = True q.append(s) while q: u = q.popleft() vis[u] = False for ed in e[u]: v, w = ed.v, ed.w if dis[v] > dis[u] + w: dis[v] = dis[u] + w cnt[v] = cnt[u] + 1 # 记录最短路经过的边数 if cnt[v] >= n: return False # 在不经过负环的情况下,最短路至多经过 n - 1 条边 # 因此如果经过了多于 n 条边,一定说明经过了负环 if not vis[v]: q.append(v) vis[v] = True
struct edge { int v, w;};vector<edge> e[MAXN];int dis[MAXN], vis[MAXN];void dijkstra(int n, int s) { memset(dis, 0x3f, (n + 1) * sizeof(int)); dis[s] = 0; for (int i = 1; i <= n; i++) { int u = 0, mind = 0x3f3f3f3f; for (int j = 1; j <= n; j++) if (!vis[j] && dis[j] < mind) u = j, mind = dis[j]; vis[u] = true; for (auto ed : e[u]) { int v = ed.v, w = ed.w; if (dis[v] > dis[u] + w) dis[v] = dis[u] + w; } }}
Python
class Edge: def __init(self, v=0, w=0): self.v = v self.w = we = [[Edge() for i in range(MAXN)] for j in range(MAXN)]INF = 0x3F3F3F3Fdef dijkstra(n, s): dis = [INF] * (n + 1) vis = [0] * (n + 1) dis[s] = 0 for i in range(1, n + 1): u = 0 mind = INF for j in range(1, n + 1): if not vis[j] and dis[j] < mind: u = j mind = dis[j] vis[u] = True for ed in e[u]: v, w = ed.v, ed.w if dis[v] > dis[u] + w: dis[v] = dis[u] + w
[!note] 优先队列实现(推荐)
[list2tab]
C++
struct edge { int v, w;};struct node { int dis, u; bool operator>(const node& a) const { return dis > a.dis; }};vector<edge> e[MAXN];int dis[MAXN], vis[MAXN];priority_queue<node, vector<node>, greater<node>> q;void dijkstra(int n, int s) { memset(dis, 0x3f, (n + 1) * sizeof(int)); memset(vis, 0, (n + 1) * sizeof(int)); dis[s] = 0; q.push({0, s}); while (!q.empty()) { int u = q.top().u; q.pop(); if (vis[u]) continue; // 已确定最短路的点直接跳过 vis[u] = 1; for (auto ed : e[u]) { int v = ed.v, w = ed.w; if (dis[v] > dis[u] + w) { // 松弛操作 dis[v] = dis[u] + w; q.push({dis[v], v}); // 注意:同一个点可能多次入队 } } }}