简介:
以前上学的时候对图论这一块总是似懂非懂的,最近在力扣上刷题,遇到了一些图论问题,正好把这些基础的图论算法重新学习整理一下。
用途:在一个有权图中(注意:不能出现负权边),求某一个点到其他所有点的最短路径
核心思想:Dijkstra(迪杰斯特拉)算法本质上是一种贪心。每次找到离源点最近的且未访问过的一个节点,用它来更新剩余所有未访问过节点到源点的距离,这种操作也叫松弛。
算法实现示例:
如上图,图中有标号从1至7的七个点及若干条边,边上的数字即为该边所表示的距离。现在要求从点1到其他点的最短路径,我们来看dijkstra算法是如何计算的。
首先,将所有不与1点直接相连的点与1的距离设为∞。
标号 | 路径 | 到源点距离 | 是否已确定最短路径 |
1 | 1 → 1 | 0 | 1 |
2 | 1 → 2 | 2 | 0 |
3 | 1 → 3 | 4 | 0 |
4 | 1 → 4 | 5 | 0 |
5 | ∞ | 0 | |
6 | ∞ | 0 | |
7 | ∞ | 0 |
此时,已确定节点只有点1。在未确定点中,点2是距离源点最近的,那么点2到源点的距离已经是最短路径(不可能有 1 -> x -> ... -> 2 这样的路径小于它, 因为 (1 -> x) > (1-> 2) )。于是把点2作为下一个已确定节点来更新其他未确定点。
具体更新的过程如下:与2直接相连的点中,1是已确定的点不用更新;点3呢,原本的路径是 1->3,距离为4,通过点2的路径为1-> 2 -> 3,该距离为1+2 = 3,比原距离短,于是更新其距离;点5呢,原本没有路径,那么直接把经过点2的路径更新进表即可,即1 -> 2 -> 5,距离为5。
标号 | 路径 | 到源点距离 | 是否已确定最短路径 |
1 | 1 → 1 | 0 | 1 |
2 | 1 → 2 | 2 | 1 |
3 | 1 → 2 → 3 | 3 | 0 |
4 | 1 → 4 | 5 | 0 |
5 | 1 → 2 → 5 | 5 | 0 |
6 | ∞ | 0 | |
7 | ∞ | 0 |
此时,点1和点2为已确定节点,我们接着寻找下一个距离最短的未确定节点,如上图所示,显然是点3。于是我们用点3来继续更新。
标号 | 路径 | 到源点距离 | 是否已确定最短路径 |
1 | 1 → 1 | 0 | 1 |
2 | 1 → 2 | 2 | 1 |
3 | 1 → 2 → 3 | 3 | 1 |
4 | 1 → 4 | 5 | 0 |
5 | 1 → 2 → 5 | 5 | 0 |
6 | 1 → 3 → 6 | 8 | 0 |
7 | ∞ | 0 |
此时未更新节点中的最短距离为5,点4和点5相同,那我们任选一个都可以,不妨选点4。于是选择点4继续更新路径。与它直接相连的未更新节点只有点6,1-> 4 -> 6的路径长度为 5 + 3 = 8,与原距离相等,可以不用更新。
标号 | 路径 | 到源点距离 | 是否已确定最短路径 |
1 | 1 → 1 | 0 | 1 |
2 | 1 → 2 | 2 | 1 |
3 | 1 → 2 → 3 | 3 | 1 |
4 | 1 → 4 | 5 | 1 |
5 | 1 → 2 → 5 | 5 | 0 |
6 | 1 → 3 → 6 | 8 | 0 |
7 | ∞ | 0 |
然后依次更新,总共经过7轮更新后,即可得到所有点到源点的最短路径。
标号 | 路径 | 到源点距离 | 是否已确定最短路径 |
1 | 1 → 1 | 0 | 1 |
2 | 1 → 2 | 2 | 1 |
3 | 1 → 2 → 3 | 3 | 1 |
4 | 1 → 4 | 5 | 1 |
5 | 1 → 2 → 5 | 5 | 1 |
6 | 1 → 3 → 6 | 8 | 1 |
7 | 1 → 2 → 5 → 7 | 9 | 1 |
Dijkstra算法的代码实现(java版本)
以力扣743-网络延迟时间这道题为例展示具体代码。
class Solution {
public int networkDelayTime(int[][] times, int n, int k) {
final int INF = Integer.MAX_VALUE / 2;
int[][] g = new int[n][n];
for(int i = 0; i < n; i++) {
Arrays.fill(g[i], INF);
}
for(int[] t : times) {
int from = t[0] - 1;
int to = t[1] - 1;
g[from][to] = t[2];
}
int[] dist = new int[n];
Arrays.fill(dist, INF);
dist[k - 1] = 0;
boolean[] used = new boolean[n];
for(int i = 0; i < n; i++) {
int x = -1;
//每次找到未确定节点中离源点最近的节点,使其为x
for(int y = 0; y < n; y++) {
if(!used[y] && (x == -1 || dist[x] > dist[y])) {
x = y;
}
}
//把x点归类为已确定节点
used[x] = true;
//用x点来更新其他所有未确定节点到源点的距离
for(int y = 0; y < n; y++) {
dist[y] = Math.min(dist[y], dist[x] + g[x][y]);
}
}
int ans = Arrays.stream(dist).max().getAsInt();
return ans == INF ? -1 : ans;
}
}