单源路径问题 全是正边 对于全是正边的题目,最短路问题上所有算法都行得通,主要看一个熟练度问题
最短 Floyd 基于动态规划的思想,从节点A到节点B的距离的最小值,等于所有途径AB之间起连通作用的点的路径的最小值,在小图里面非常好写,时间复杂度 $O(n^3)$
1 2 3 4 for k in range (1 , N + 1 ): for i in range (1 , N + 1 ): for j in range (1 , N + 1 ): f[i][j] = min (f[i][j], f[i][k] + f[k][j])
Bellman-Ford 每一轮都对边进行松弛操作 $dis(v) = min(dis(v), dis(u) + w(u, v))$ ,如果有负环则可以一直松弛下去,如果最后发现最后一轮还在松弛,说明进入了负环,时间复杂度是 $O(mn)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 es = [[] for _ in range (N)] dis = [inf] * N bellman(n, s): dis[s] = 0 f = False for i in range (1 , n + 1 ): for st in range (1 , n + 1 ): for to in es[st]: ed, w = to[0 ], to[1 ] if dis[ed] > dis[st] + w: dis[ed] = dis[st] + w f = True if not f: break return f
SPFA 对于Bellman算法,我们是不需要松弛所有边的,只有上一次松弛后的节点连接的边,才有可能被下一次松弛,因此可以使用队列进行优化,它只是上述算法的实现,时间复杂度不变
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 private boolean spfa (int n, int s) { boolean [] vi = new int [n + 1 ]; int [] cnt = new int [n + 1 ]; vi[s] = true ; cnt[s] = 1 ; int [] dis = new int [n + 1 ]; dis[s] = 0 ; Queue<Integer> q = new ArrayDeque <>(); q.offer(s); while (!q.isEmpty()) { int i = q.poll(); vi[i] = false ; for (var to : es[i]) { int j = to[0 ], w = to[1 ]; if (dis[j] > dis[i] + w) { dis[j] = dis[i] + w; cnt[j] = cnt[i] + 1 ; if (cnt[j] >= n) return true ; if (!vi[j]) { q.offer(j); vi[j] = true ; } } } } return false ; }
Dijkstra 最经典的最短路算法,使用优先队列优化,每次选择最小的状态进行松弛,时间复杂度 $O(mlogm)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 static int [] dis = new int [N];static boolean [] vi = new boolean [N];static int inf = Integer.MAX_VALUE;static class Node { int i, w; public Node (int i, int w) { this .i = i; this .w = w; } } private void dijkstra (int s) { PriorityQueue<Node> q = new PriorityQueue <>((x, y) -> x.w - y.w); dis[s] = 0 ; Arrays.fill(dis, 1 , n + 1 , inf); q.offer(new Node (s, 0 )); while (!q.isEmpty()) { Node now = q.poll(); int i = now.i; if (vi[i]]) continue ; vi[i] = true ; for (var to : es[i]) { int j = to[0 ], w = to[1 ]; Node nxt = new Node (j, dis[i] + w); if (nxt.w < dis[j]) { dis[j] = nxt.w; q.offer(nxt); } } } }
最长 由于我们讨论的图全是正边,对于所有的算法, 都可以通过把边全部取得相反数,然后跑最短路来获得绝对值最大的路,然后返回结果的相反数即可
存在负数边 最短 存在负数边的情况下,上述的算法Dijkstra无法进行最短路的求解了,那么同理它在有负数边的情况下也无法求解最长路
最长 上述算法除了Dijkstra都可以求解最长路
DAG 对于特殊的DAG而言,可以通过拓扑和动态规划更快地求得结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 es = [[] for _ in range (N)] q = queue.Queue() a = [] ind = [0 ] * n for i in range (n): for e in es[i]: ind[e[0 ]] += 1 for i in range (n): if ind[i] == 0 : q.put(i) while not q.empty(): i = q.get() a.append(i) for to in es[i]: ind[to[0 ]] -= 1 if ind[to[0 ]] == 0 : q.put(to[0 ]) mx = [-inf] * n mn = [inf] * n def f (s ): mx[s] = 0 mn[s] = 0 for i in a: for to in es[i]: j, w = to[0 ], to[1 ] mx[j] = max (mx[j], mx[i] + w) mn[j] = min [mn[j], mx[i] + w]