dijkstra(堆优化版)精讲 — 卡码网:47. 参加科学大会
题目链接:https://kamacoder.com/problempage.php?pid=1047
文档讲解:https://programmercarl.com/kamacoder/0047.%E5%8F%82%E4%BC%9Adijkstra%E5%A0%86.html
思路
当节点数多,边数少(稀疏图)时,可以考虑从边的角度出发,用堆来优化dijkstra算法。dijkstra算法三部曲为:
- 第一步,选源点到哪个节点近且该节点未被访问过
- 第二步,该最近节点被标记访问过
- 第三步,更新非访问节点到源点的距离(即更新minDist数组)
那么当从边的角度出发, 在处理三部曲里的第一步(选源点到哪个节点近且该节点未被访问过)的时候 ,我们可以不用去遍历所有节点了。而且直接把边(带权值)加入到小顶堆(利用堆来自动排序),那么每次我们从堆顶里取出边自然就是距离源点最近的节点所在的边。这样我们就不需要两层for循环来寻找最近的节点了。
代码
import java.util.*;
class Edge {
int to; // 邻接顶点
int val; // 边的权重
Edge(int t, int w) {
this.to = t;
this.val = w;
}
}
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
List<List<Edge>> grid = new ArrayList<>();
for (int i = 0; i <= n; i++) {
grid.add(new ArrayList<>());
}
for (int i = 0; i < m; i++) {
int p1 = sc.nextInt();
int p2 = sc.nextInt();
int val = sc.nextInt();
grid.get(p1).add(new Edge(p2, val));
}
int start = 1; // 起点
int end = n; // 终点
// 存储从源点到每个节点的最短距离
int[] minDist = new int[n + 1];
Arrays.fill(minDist, Integer.MAX_VALUE);
// 记录顶点是否被访问过
boolean[] visited = new boolean[n + 1];
// 优先队列中存放 int[]{节点,源点到该节点的距离}
PriorityQueue<int[]> pq = new PriorityQueue<>(new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) { // o1[0]中放节点,o1[1]中放圆源点到该节点的距离
return o1[1] - o2[1]; // 小顶堆
}
});
// 初始化队列,源点到源点的距离为0,所以初始为0
pq.offer(new int[]{start, 0});
minDist[start] = 0; // 起始点到自身的距离为0
while (!pq.isEmpty()) {
// 1. 第一步,选源点到哪个节点近且该节点未被访问过 (通过优先级队列来实现)
int[] cur = pq.poll();
if (visited[cur[0]]) continue;
// 2. 第二步,该最近节点被标记访问过
visited[cur[0]] = true;
// 3. 第三步,更新非访问节点到源点的距离(即更新minDist数组)
for (Edge edge : grid.get(cur[0])) { // 遍历 cur 指向的节点,cur 指向的节点为 edge
if (!visited[edge.to] && minDist[cur[0]] + edge.val < minDist[edge.to]) { // 更新 minDist
minDist[edge.to] = minDist[cur[0]] + edge.val;
pq.offer(new int[]{edge.to, minDist[edge.to]});
}
}
}
if (minDist[end] == Integer.MAX_VALUE) {
System.out.println(-1); // 不能到达终点
} else {
System.out.println(minDist[end]); // 到达终点最短路径
}
}
}
Bellman_ford 算法精讲 — 卡码网:94. 城市间货物运输 I
题目链接:https://kamacoder.com/problempage.php?pid=1152
文档讲解:https://programmercarl.com/kamacoder/0094.%E5%9F%8E%E5%B8%82%E9%97%B4%E8%B4%A7%E7%89%A9%E8…
思路
本题中边的权值有负数,是带负权值的单源最短路问题,所以用到Bellman_ford 算法。Bellman_ford算法的核心思想是对所有边进行松弛n-1次操作(n为节点数量),从而求得目标最短路。
什么是松弛?
minDist[B]
表示到达B节点最小权值。如果通过 A 到 B 这条边可以获得更短的到达B节点的路径,即如果minDist[B] > minDist[A] + value
,那么我们就更新minDist[B] = minDist[A] + value
,这个过程就叫做 “松弛” 。也可以这么写:minDist[B] = min(minDist[A] + value, minDist[B])
。
Bellman_ford算法也是采用了动态规划的思想,即:将一个问题分解成多个决策阶段,通过状态之间的递归关系最后计算出全局最优解。
为什么要对所有边松弛n-1次?
对所有边松弛一次 能得到 与起点 一条边相连的节点最短距离。
对所有边松弛两次 可以得到与起点 两条边相连的节点的最短距离。
对所有边松弛三次 可以得到与起点 三条边相连的节点的最短距离。
那么需要对所有边松弛n-1次才能得到 起点(节点1) 到终点(节点n)的最短距离。
代码
import java.util.*;
class Main{
public static void main (String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt(), m = in.nextInt();
int[][] grid = new int[m][3];
int[] minDist = new int[n + 1];
Arrays.fill(minDist, Integer.MAX_VALUE);
for (int i = 0; i < m; i++) {
grid[i][0] = in.nextInt();
grid[i][1] = in.nextInt();
grid[i][2] = in.nextInt();
}
int start = 1, end = n;
minDist[start] = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
int from = grid[j][0], to = grid[j][1], price = grid[j][2];
if (minDist[from] != Integer.MAX_VALUE && minDist[to] > minDist[from] + price) {
minDist[to] = minDist[from] + price;
}
}
}
System.out.println(minDist[end] == Integer.MAX_VALUE ? "unconnected" : minDist[end]);
}
}