Bootstrap

代码随想录算法训练营第六十四天 | 拓扑排序、dijkstra(朴素版)

拓扑排序 — 卡码网:117. 软件构建

题目链接:https://kamacoder.com/problempage.php?pid=1191
文档讲解:https://programmercarl.com/kamacoder/0117.%E8%BD%AF%E4%BB%B6%E6%9E%84…

思路

本题是拓扑排序的经典题目。给出一个有向图,把这个有向图转成线性的排序就叫拓扑排序。当然拓扑排序也要检测这个有向图是否有环,即存在循环依赖的情况,因为这种情况是不能做线性排序的。所以拓扑排序也是图论中判断有向无环图的常用方法
实现拓扑排序的算法有两种:卡恩算法(BFS)和DFS,先主要掌握BFS。
拓扑排序的过程,其实就两步:

  • 找到入度为0 的节点,加入结果集。
  • 将该节点从图中移除。

循环以上两步,直到 所有节点都在图中被移除了。结果集的顺序,就是我们想要的拓扑排序顺序。如果我们发现结果集元素个数 不等于 图中节点个数,我们就可以认定图中一定有有向环

代码

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[] inDegree = new int[n]; //记录入度
        List<Integer> res = new ArrayList<>();
        HashMap<Integer, List<Integer>> fileMap = new HashMap<>(); //记录文件依赖
        for (int i = 0; i < m; i++) {
            int s = in.nextInt(), t = in.nextInt();
            inDegree[t]++;
            // fileMap.computeIfAbsent(s, k -> new ArrayList<>()).add(t); 会超时
            List<Integer> list = new ArrayList<>();
            if (!fileMap.containsKey(s)) list.add(t);
            else {
                list = fileMap.get(s);
                list.add(t);
            }
            fileMap.put(s, list);
        }
        Queue<Integer> queue = new LinkedList<>(); //记录入度为0的文件
        for (int i = 0; i < n; i++) {
            if (inDegree[i] == 0) queue.offer(i); //先把入度为0的文件放入队列
        }
        while (!queue.isEmpty()) {
            int cur = queue.poll();
            res.add(cur); 
            if (fileMap.containsKey(cur)) {
                List<Integer> files = fileMap.get(cur); //获取入度为0的文件的依赖文件
                for (int file : files) {
                    inDegree[file]--; //将依赖文件的入度减1
                    if (inDegree[file] == 0) queue.offer(file); //收集下一轮入度为0的文件
                }
            }
        }
        if (res.size() == n) {
            for (int i = 0; i < n - 1; i++) System.out.print(res.get(i) + " ");
            System.out.print(res.get(n - 1));
        } else System.out.println("-1"); // 结果中文件数不够,说明剩下的成环了
    }
}

dijkstra(朴素版) — 卡码网:47. 参加科学大会

题目链接:https://kamacoder.com/problempage.php?pid=1047
文档讲解:https://programmercarl.com/kamacoder/0047.%E5%8F%82%E4%BC%9Adijkstra%E6%9C…

思路

dijkstra算法:在有权图(权值非负数)中求从起点到其他节点的最短路径算法。
需要注意两点:

  • dijkstra 算法可以同时求起点到所有节点的最短路径
  • 权值不能为负数

dijkstra 算法同样是贪心的思路,不断寻找距离源点最近的没有访问过的节点。dijkstra三部曲:

  • 第一步,选源点到哪个节点近且该节点未被访问过
  • 第二步,该最近节点被标记访问过
  • 第三步,更新非访问节点到源点的距离(即更新minDist数组)

minDist数组用来记录每一个节点距离源点的最小距离。初始化时初始为最大值。

代码

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[n + 1][n + 1];
        for (int i = 0; i <= n; i++) Arrays.fill(grid[i], Integer.MAX_VALUE);
        int[] minDist = new int[n + 1];
        boolean[] visited = new boolean[n + 1];
        for (int i = 0; i < m; i++) {
            int s = in.nextInt(), t = in.nextInt();
            grid[s][t] = in.nextInt();
        }
        int start = 1, end = n;
        Arrays.fill(minDist, Integer.MAX_VALUE);
        minDist[start] = 0;
        for (int i = 1; i <= n; i++) {
            int minVal = Integer.MAX_VALUE, cur = 1;
            for (int j = 1; j <= n; j++) { //第一步,找到离起点距离最近的点
                if (!visited[j] && minDist[j] < minVal) {
                    minVal = minDist[j];
                    cur = j;
                }
            }
            visited[cur] = true; // 第二步,将这个点设为已访问
            for (int k = 1; k <= n; k++) { // 第三步,更新minDist
                if (!visited[k] && grid[cur][k] != Integer.MAX_VALUE 
                && minDist[cur] + grid[cur][k] < minDist[k]) {
                    minDist[k] = minDist[cur] + grid[cur][k];
                }
            }
            
        }
        System.out.println(minDist[end] == Integer.MAX_VALUE ? -1 : minDist[end]);
    }
}
;