代码随想录算法训练营第 57 天 | Dijkstra(堆优化版)精讲、Bellman_ford 算法精讲

Dijkstra(堆优化版)精讲

朴素版时间复杂度:O(V^2)

堆优化版相比朴素版采用了两个优化:

  1. 在 minDist 中找最小值优化成小顶堆。
  2. 采用邻接表代替邻接矩阵。更新节点到源点距离时从边出发而不是点出发,在点多边少的情况下更加高效。

堆优化版时间复杂度: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);
        }
    }
}

朴素法和堆优化版写法上的小细节区别:

  1. 朴素法 graph 和 minDist 都要初始化为 max;堆优化版因为用的邻接表,所以只有 minDist 需要初始化为 max。
  2. 朴素法需要判断 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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值