在图论中,最短路径问题是非常常见的基本问题之一。Dijkstra 算法是解决单源最短路径问题中最经典、最常用的算法之一,适用于带权有向图,边权非负的情况。
本文将结合一段完整的 Java 实现,带你从原理到代码逐步深入掌握 Dijkstra 算法。
Dijkstra 的目标是:给定起点 $S$,找到图中从 $S$ 到所有点的最短路径长度。
它的核心思想是:
贪心 + 已知最短路径的节点不会再被更新
初始化:
创建一个距离数组 dist[]
,记录起点到每个点的当前最短距离;
初始时 dist[start] = 0
,其余点为 ∞
;
使用优先队列(最小堆)维护未处理的节点,优先选择距离最短的;
每次从优先队列中取出当前最近的节点 u
,进行如下操作:
遍历 u
的所有邻接边 (u → v, w)
,如果 dist[v] > dist[u] + w
,就更新并将 v
加入队列;
标记 u
为已访问,之后不会再处理它;
重复此过程直到队列为空,dist[]
中就是从起点到所有点的最短路径。
我们证明:
假设从起点到节点 u
的最短路径已找到(dist[u]
最小),则:
若存在比当前路径更短的路径,那一定是通过其他未被锁定的节点;
但根据优先队列机制,其他节点的距离都不可能更小(否则会先被选中);
所以当前的 dist[u]
必定是最优的。
这就是 Dijkstra 的贪心选择是**“局部最优 = 全局最优”**的典范。
以图为例(起点为 0):
0 --(4)--> 1 --(2)--> 2
\ ↘︎
(1) (5)
↓ ↘︎
3 --(3)--> 4
初始:dist = [0, ∞, ∞, ∞, ∞]
,pq = [(0, 0)]
弹出点 0,更新点 1(0+4=4
),点 3(0+1=1
),pq = [(3,1), (1,4)]
弹出点 3,更新点 4(1+3=4
),pq = [(1,4), (4,4)]
弹出点 1,更新点 2(4+2=6
),点 4(不更新,因为 4 < 9
),pq = [(4,4), (2,6)]
...以此类推
每次都是从未访问的点中,挑出距离最小的那个,从它继续扩展更新。
使用最小堆优化后的 Dijkstra 算法的时间复杂度为:
O((V + E) log V)
V
是节点数;
E
是边数;
每个节点最多被加入优先队列一次,每次操作代价是 log V
;
每条边最多被检查一次。
下面是完整实现,并结合关键段落做详细解释:
package ff;
import java.io.*;
import java.util.*;
public class Main {
static FastRead in = new FastRead();
static PrintWriter out = new PrintWriter(System.out);
static class Edge {
int to;
int val;
public Edge(int to, int val) {
this.to = to;
this.val = val;
}
}
public static void main(String[] args) {
int n = in.nextInt(); // 节点数
int m = in.nextInt(); // 边数
List> graph = new ArrayList<>();
for (int i = 0; i < n; i++) {
graph.add(new ArrayList<>()); // 初始化邻接表
}
for (int i = 0; i < m; i++) {
int u = in.nextInt() - 1;
int v = in.nextInt() - 1;
int w = in.nextInt();
graph.get(u).add(new Edge(v, w)); // 有向边 u->v
}
int[] dist = dij(n, graph, 0);
int ans = dist[n - 1] == Integer.MAX_VALUE ? -1 : dist[n - 1];
out.println(ans);
out.flush();
out.close();
}
public static int[] dij(int n, List> graph, int start) {
int[] dist = new int[n];
Arrays.fill(dist, Integer.MAX_VALUE);
dist[start] = 0;
PriorityQueue pq = new PriorityQueue<>(Comparator.comparingInt(a -> a[1]));
pq.offer(new int[] { start, 0 });
boolean[] visited = new boolean[n];
while (!pq.isEmpty()) {
int[] cur = pq.poll();
int u = cur[0];
int d = cur[1];
if (visited[u]) continue;
visited[u] = true;
for (Edge edge : graph.get(u)) {
int v = edge.to;
int w = edge.val;
if (dist[v] > d + w) {
dist[v] = d + w;
pq.offer(new int[] { v, dist[v] });
}
}
}
return dist;
}
}
class FastRead {
StringTokenizer st;
BufferedReader br;
public FastRead() {
br = new BufferedReader(new InputStreamReader(System.in));
}
String next() {
while (st == null || !st.hasMoreElements()) {
try {
st = new StringTokenizer(br.readLine());
} catch (IOException e) {
e.printStackTrace();
}
}
return st.nextToken();
}
int nextInt() {
return Integer.parseInt(next());
}
}
使用 优先队列 维护最短路径;
dist[]
存储最短路径长度;
visited[]
标记已经确定最短路径的节点;
每次从队列中取出当前最小距离的点进行扩展。
输入样例:
5 6
1 2 2
1 3 4
2 3 1
2 4 7
3 5 3
4 5 1
说明:
有 5 个点,6 条边;
边表示为 起点 终点 权重
,从点 1 开始到点 5 的最短路径。
输出样例:
8
✅ 如果图是无向图,加边时双向:
graph.get(u).add(new Edge(v, w));
graph.get(v).add(new Edge(u, w));
✅ 想输出路径的话,可以维护一个 pre[]
数组记录每个点的前驱,最后回溯路径。
Dijkstra 算法适用于边权非负的有向图或无向图;
通过优先队列可以将时间复杂度降为 O((V + E) log V)
;
实现时要注意下标从 0 开始,避免越界;
常用于图论入门、路径优化、交通网络等场景。
Leetcode 743. 网络延迟时间
AcWing 849. Dijkstra求最短路 I
洛谷 P3371 【模板】单源最短路径(弱化版)
如果这篇文章对你有帮助,欢迎 点赞 + ⭐收藏 + 评论,让更多人一起掌握图论的核心算法!有任何问题也欢迎留言交流哦~