代码随想录算法训练营第六十五天 | dijkstra(堆优化版)精讲、Bellman_ford 算法精讲、复习

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]);
    }
}

复习二叉树部分

654.最大二叉树
617.合并二叉树
700.二叉搜索树中的搜索
98.验证二叉搜索树

  • 22
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值