最短路问题,可以分为单源最短路径和多源最短路径问题
所有边的权值为正的情况下,可以使用 Dijkstra 算法
n 为点的数量, m 为边的数量
存在负权边,可以使用
Dijkstra算法
求源点到其他所有点的距离的最小值
时间复杂度为 O(n2)
算法流程
1.初始化距离数组 Dist[] ,源点到源点距离为0,其余赋为 INF
维持一个集合 S ,保存着已求出最短距离的节点
2.迭代 n - 1 次:
找到尚未加入集合 S 中的,距离源点最近的点 t ,加入集合 S 中
然后用新加入的点 t 更新源点到其他点的距离
即比较 Dist{orig→the others} 和 Dist{orig→t→the others} 哪个更小
这一步被称为「松弛操作」
如何形象地理解最短路算法中“松弛”的含义? - 李欣宜的回答 - 知乎
算法证明
Dijkstra 算法中,每一轮迭代后,将点 ver 加入集合 S ,此时
dist[ver]=shortest distance of <orig→ver>
可以用反证法证明:
如果此时至少存在一个尚未加入集合 S 的 t ,使得 Dist{orig→t→ver}<Dist{orig→ver}
则 Dist{orig→t}<Dist{orig→ver} ,那么节点 t 应当已经加入了集合 S ,与假设不符合,证毕
值得注意的是
在 Dist{orig→t→ver}<Dist{orig→ver} 的情况下,如果边 edge<t→ver> 权值为负,是推不出来 Dist{orig→t}<Dist{orig→ver} 的
这也就是为什么 Dijkstra 算法只能适用于所有边的权值为正的图
代码实现
算法核心代码
ps:以下代码是针对稠密图,采用邻接矩阵存储
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| int[] dijkstra() { Arrays.fill(dist, INF); dist[1] = 0; for (int i = 1; i < n; ++i) { int t = -1; for (int j = 1; j <= n; ++j) { if (!st[j] && (t == -1 || dist[j] < dist[t])) t = j; } for (int j = 1; j <= n; ++j) { dist[j] = Math.min(dist[j], dist[t] + g[t][j]); } st[t] = true; } return dist; }
|
java版完整代码
acwing 849. Dijkstra求最短路 I
求1
号点到n
号点的最短距离
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 31 32 33 34 35 36 37 38 39 40 41 42 43
| import java.util.*; import java.io.*;
class Main{ static final int INF = 0x3f3f3f3f; static BufferedReader cin = new BufferedReader(new InputStreamReader(System.in)); static final int N = 510; static int[][] g = new int[N][N]; static boolean[] st = new boolean[N]; static int[] dist = new int[N]; static int n, m; static int dijkstra() { for (int i = 1; i < n; ++i) { int t = -1; for (int j = 1; j <= n; ++j) { if (!st[j] && (t == -1 || dist[j] < dist[t])) t = j; } for (int j = 1; j <= n; ++j) { dist[j] = Math.min(dist[j], dist[t] + g[t][j]); } st[t] = true; } return dist[n] == INF ? -1 : dist[n]; } public static void main(String[] args) throws Exception { String[] tt = cin.readLine().split(" "); n = Integer.parseInt(tt[0]); m = Integer.parseInt(tt[1]); String[] strs; int a, b, w; for (int i = 0; i < n; ++i) Arrays.fill(g[i+1], INF); for (int i = 0; i < m; ++i) { strs = cin.readLine().split(" "); a = Integer.parseInt(strs[0]); b = Integer.parseInt(strs[1]); w = Integer.parseInt(strs[2]); g[a][b] = Math.min(g[a][b], w); } Arrays.fill(dist, INF); dist[1] = 0; System.out.println(dijkstra()); cin.close(); } }
|
C++代码
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
| const int N = 510;
int n, m; int g[N][N]; int dist[N]; bool st[N];
int dijkstra() { memset(dist, 0x3f, sizeof dist); dist[1] = 0;
for (int i = 0; i < n - 1; i ++ ) { int t = -1; for (int j = 1; j <= n; j ++ ) if (!st[j] && (t == -1 || dist[t] > dist[j])) t = j;
for (int j = 1; j <= n; j ++ ) dist[j] = min(dist[j], dist[t] + g[t][j]);
st[t] = true; }
if (dist[n] == 0x3f3f3f3f) return -1; return dist[n]; }
|
堆优化
优化思路
整个算法步骤中
寻找最小距离是 O(n2) 的,可以用堆存储距离,优化为 O(n) (每一次是 O(1) 的)
使用堆存储距离后,单次修改操作是 O(logn) 的,一共修改 m 次
具体实现
使用优先队列,由于优先队列不支持修改元素,需要更新距离时,直接插入一个新元素。这样,优先队列中可能会有 m 个元素,因此总复杂度为 O(mlogm)
用数组模拟可以修改任意元素的堆,需要做映射,实现繁琐,代码参见模拟堆
复杂度分析
在图中,边的数量 m 最多为 n∗(n-1) ,数量级为 n2
mlogm=Θ(mlogn2)
且 mlogn2=2mlogn,因此 mlogn=Θ(mlogm) ,使用JDK
自带的优先队列即可
代码
算法核心代码
ps:邻接表方式存储图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| int[] dijkstra() { Arrays.fill(dist, INF); dist[1] = 0; PriorityQueue<int[]> heap = new PriorityQueue<>(n, (a,b) -> {return a[1] - b[1];}); heap.offer(new int[]{1, 0}); while (!heap.isEmpty()) { int[] cur = heap.poll(); int ver = cur[0]; int distance = cur[1]; if (st[ver]) continue; st[ver] = true; for (int i = h[ver]; i != -1; i = ne[i]) { int j = e[i]; if (dist[j] > distance + w[i]) { dist[j] = distance + w[i]; heap.offer(new int[]{j, dist[j]}); } } } return dist; }
|
java版完整代码
acwing 850. Dijkstra求最短路 II
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| import java.util.*; import java.io.*;
class Main{ static final int INF = 0x3f3f3f3f; static BufferedReader cin = new BufferedReader(new InputStreamReader(System.in)); static final int N = (int) 150010; static int[] e = new int[N]; static int[] ne = new int[N]; static int[] h = new int[N]; static int[] w = new int[N]; static int idx; static boolean[] st = new boolean[N]; static int[] dist = new int[N]; static int n, m; static void add(int a, int b, int c) { e[idx] = b; w[idx] = c; ne[idx] = h[a]; h[a] = idx++; } static int dijkstra() { Arrays.fill(dist, INF); dist[1] = 0; PriorityQueue<int[]> heap = new PriorityQueue<>(n, (a,b) -> {return a[1] - b[1];}); heap.offer(new int[]{1, 0}); while (!heap.isEmpty()) { int[] cur = heap.poll(); int ver = cur[0]; int distance = cur[1]; if (st[ver]) continue; st[ver] = true; for (int i = h[ver]; i != -1; i = ne[i]) { int j = e[i]; if (dist[j] > distance + w[i]) { dist[j] = distance + w[i]; heap.offer(new int[]{j, dist[j]}); } } } return dist[n] == INF ? -1 : dist[n]; } public static void main(String[] args) throws Exception { String[] strs = cin.readLine().split("\\s+"); n = Integer.parseInt(strs[0]); m = Integer.parseInt(strs[1]); int a, b, w; Arrays.fill(h, -1); for (int i = 0; i < m; ++i) { strs = cin.readLine().split("\\s+"); a = Integer.parseInt(strs[0]); b = Integer.parseInt(strs[1]); w = Integer.parseInt(strs[2]); add(a, b, w); } System.out.println(dijkstra()); cin.close(); } }
|
C++完整代码
acwing 850. Dijkstra求最短路 II
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| #include <cstring> #include <iostream> #include <algorithm> #include <queue>
using namespace std;
typedef pair<int, int> PII;
const int N = 1e6 + 10;
int n, m; int h[N], w[N], e[N], ne[N], idx; int dist[N]; bool st[N];
void add(int a, int b, int c) { e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ; }
int dijkstra() { memset(dist, 0x3f, sizeof dist); dist[1] = 0; priority_queue<PII, vector<PII>, greater<PII>> heap; heap.push({0, 1});
while (heap.size()) { auto t = heap.top(); heap.pop();
int ver = t.second, distance = t.first;
if (st[ver]) continue; st[ver] = true;
for (int i = h[ver]; i != -1; i = ne[i]) { int j = e[i]; if (dist[j] > dist[ver] + w[i]) { dist[j] = dist[ver] + w[i]; heap.push({dist[j], j}); } } }
if (dist[n] == 0x3f3f3f3f) return -1; return dist[n]; }
int main() { scanf("%d%d", &n, &m);
memset(h, -1, sizeof h); while (m -- ) { int a, b, c; scanf("%d%d%d", &a, &b, &c); add(a, b, c); }
cout << dijkstra() << endl;
return 0; }
|
📔博文图谱
提及本博文的链接