Dijkstra(堆优化版)精讲
朴素版时间复杂度:O(V^2)
堆优化版相比朴素版采用了两个优化:
- 在 minDist 中找最小值优化成小顶堆。
- 采用邻接表代替邻接矩阵。更新节点到源点距离时从边出发而不是点出发,在点多边少的情况下更加高效。
堆优化版时间复杂度:O(E * logE)
每个边只会被访问一次 + 堆的开销
重点:minDist[1] 要初始化为 0。
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int k = sc.nextInt();
List<List<int[]>> graph = new ArrayList<>(); // 采用邻接表,权值存在 int[] 中
for (int i = 0; i < n + 1; i++) {
graph.add(new ArrayList<>());
}
for (int i = 0; i < k; i++) {
int s = sc.nextInt();
int t = sc.nextInt();
int val = sc.nextInt();
graph.get(s).add(new int[] {t, val});
}
int start = 1, end = n;
boolean[] visited = new boolean[n + 1];
int[] minDist = new int[n + 1];
Arrays.fill(minDist, Integer.MAX_VALUE);
minDist[1] = 0; // 必须初始化为 0
// 创建小顶堆,按权值排序
PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[1] - b[1]); // 存储 to 和**到源点**的 value(普通的 int[] 存储的是 t 和到 s 的value)
pq.add(new int[] {start, 0}); // 初始加入源点和距离 0
while (!pq.isEmpty()) {
// 第一步:选距离源点最近且未访问的点
int[] cur = pq.poll();
int from = cur[0];
// 第二步:标记为访问过
visited[from] = true;
// 第三步:更新所有相连的、**未访问**的节点到源点距离
for (int[] edge : graph.get(from)) {
int to = edge[0];
int value = edge[1];
if (!visited[to] && minDist[from] + value < minDist[to]) { // 不用再担心 max 溢出
minDist[to] = minDist[from] + value;
pq.offer(new int[] {to, minDist[to]}); // 把新的 to 和更新后的到源点的距离 添加到 pq
}
}
}
if (minDist[end] != Integer.MAX_VALUE) {
System.out.println(minDist[end]);
} else {
System.out.println(-1);
}
}
}
朴素法和堆优化版写法上的小细节区别:
- 朴素法 graph 和 minDist 都要初始化为 max;堆优化版因为用的邻接表,所以只有 minDist 需要初始化为 max。
- 朴素法需要判断
graph[cur][j] != Integer.MAX_VALUE;堆优化法不需要判断,因为是从邻接表中取出节点,肯定是相连的。
Bellman_ford 算法精讲
可以解决带负权值的问题。但是不能出现负权值环路。
对所有边松弛 n - 1 次。(因为源点通过 n - 1 条边肯定可以到达终点。)
时间复杂度:O(V * E)
步骤:
对题目给出的 n 条边依次松弛,总共松弛 n - 1 轮。
需要判断 minDist[from] 是否是 max,否则松弛没有意义,而且加法会溢出
松弛:
if (minDist[A] > minDist[C] + val) minDist[A] = minDist[C] +val
松弛一次:从源点出发,与源点一条边相连的节点的最短距离。
松弛两次:从源点出发,与源点两条边相连的节点的最短距离。
松弛 n - 1 次:将所有点到源点的最短距离都更新完毕。
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int k = sc.nextInt();
List<int[]> graph = new ArrayList<>(); // 采用朴素法,int[] 存 s、t、val
for (int i = 0; i < k; i++) {
int s = sc.nextInt();
int t = sc.nextInt();
int val = sc.nextInt();
graph.add(new int[] {s, t, val});
}
int start = 1, end = n;
int[] minDist = new int[n + 1];
Arrays.fill(minDist, Integer.MAX_VALUE);
minDist[1] = 0; // 必须初始化为 0
for (int i = 1; i < n; i++) { // 松弛 n - 1 轮
for (int[] edge : graph) {
int from = edge[0];
int to = edge[1];
int val = edge[2];
if (minDist[from] != Integer.MAX_VALUE && minDist[from] + val < minDist[to]) { // 必须先判断 minDist[from] != Integer.MAX_VALUE,防止从未计算过的节点出发,也防溢出
minDist[to] = minDist[from] + val;
}
}
}
if (minDist[end] != Integer.MAX_VALUE) {
System.out.println(minDist[end]);
} else {
System.out.println("unconnected");
}
}
}
在判断不等于 max 上的小总结:
- Dijkstra 朴素法需要判断 graph[cur][j] != Integer.MAX_VALUE(当前节点和它连的点的距离不为 max)
- Dijkstra 堆优化法不需要判断此
- Bellman_ford 算法需要判断 minDist[from] != Integer.MAX_VALUE(当前节点到源点的最短距离不为 max)
1248

被折叠的 条评论
为什么被折叠?



