拓扑排序 — 卡码网: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]);
}
}