拓扑排序算法: 拓扑排序就是找到一个入度为0的带你加入队列后,在图中去掉该点及其与它相连的边,然后再词寻找入度为零的点直到所有的点都被遍历,由上面的过程中我们不难想到,如果图中存在环这种结构那么我们是无法遍历所有的节点的,所以拓扑排序的一个重大的应用就是可以找出不存在与环中的点
例题1 在有向图中,以某个节点为起始节点,从该点出发,每一步沿着图中的一条有向边行走。如果到达的节点是终点(即它没有连出的有向边),则停止。
对于一个起始节点,如果从该节点出发,无论每一步选择沿哪条有向边行走,最后必然在有限步内到达终点,则将该起始节点称作是 安全 的。
返回一个由图中所有安全的起始节点组成的数组作为答案。答案数组中的元素应当按 升序 排列。
该有向图有 n 个节点,按 0 到 n - 1 编号,其中 n 是 graph 的节点数。图以下述形式给出:graph[i] 是编号 j 节点的一个列表,满足 (i, j) 是图的一条有向边。(力扣:802)
首先这道题要理解什么是安全点:首先题目中说明了安全点就是无论然那条路径最终都可以到达出度为零的点,所以处于环中的点肯定就不是安全点,所以我们就是要找到入度为零的点所以自然而然的就想到了拓扑排序,不过这道题需要变通的点就在于将图反序,以原图中出度为零的点作为起点
// 一种新的解题思路:拓扑序列,简单来说其实拓扑序列的作用就是能提取出不处与环中的节点
// 而题目要求我们求什么?就是求出度为零的点,以及最终指向出度为零,且不处于环中的节点
// 所以我们可以将逆序,然后求出以终点为起点的拓扑序列,最终不处于该序列中的点即是处于环中的点
class Solution {
// 创建一个集合来保存所有安全的起点
List<Integer> alllist=new ArrayList<Integer>();
// 创建一个集合来保存返图的节点关系
List<List<Integer>> regraph=new ArrayList<List<Integer>>();
public List<Integer> eventualSafeNodes(int[][] graph) {
// 首先为反图集合赋予空间
for(int i=0;i<graph.length;i++){
regraph.add(new ArrayList<Integer>());
}
// 将图中的关系保存到反图中去
for(int i=0;i<graph.length;i++){
for(int j=0;j<graph[i].length;j++){
regraph.get(graph[i][j]).add(i);
}
}
// 保存好反图中各节点的入度的关系,也就是原图中该节点的出度的关系
int[] edge=new int[graph.length];
for(int i=0;i<graph.length;i++){
edge[i]=graph[i].length;
}
// 进行拓扑排序
// 创建一个队列进行拓扑排序
Queue<Integer> queue=new LinkedList<Integer>();
// 首先找到入度为零的点并将这些节点如队列
for(int i=0;i<edge.length;i++){
if(edge[i]==0){
queue.offer(i);
}
}
// 进行广度优先遍历
while(!queue.isEmpty()){
int temp=queue.poll();
// 将与当前节点相邻的节点的入入度都减一,并且将减一后入度为0的节点加入队列
List<Integer> temp1=regraph.get(temp);
for(int k:temp1){
edge[k]--;
if(edge[k]==0){
queue.offer(k);
}
}
}
// 最后遍历一遍edge数组如果该节点的入度为零则说明该点不处于环中,该点为安全点
for(int i=0;i<edge.length;i++){
if(edge[i]==0){
alllist.add(i);
}
}
return alllist;
}
}
例题2:杂物
import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.Scanner;
public class Main{
public static void main(String[] args) {
Scanner scan=new Scanner(System.in);
int n=scan.nextInt();
// 创建一个集合保存只完成该项项目所需要的时间
int[] time=new int[n+1];
List<Integer>[] map=new List[n+1];
// 为图赋予空间
for(int i=0;i<=n;i++) {
map[i]=new ArrayList<Integer>();
}
// 创建一个数组保存每个点的入度的关系
int[] edge=new int[n+1];
// 利用循环输时间以及图论之间的关系
for(int i=1;i<n+1;i++) {
int temp=scan.nextInt();
time[temp]=scan.nextInt();
while(temp!=0) {
int a=scan.nextInt();
if(a==0) break;
// 因为后面保存的是前置的节点
map[a].add(temp);
edge[i]++;
}
}
// for(int i=1;i<=n;i++) {
// for(int a:map[i]) {
// System.out.print(a+" ");
// }
// System.out.println();
// }
// 创建一个数组保存完成每项杂物所需的时间
int[] res=new int[n+1];
// 建图完毕,创建一个队列进行广度优先遍历
Deque<Integer> queue=new LinkedList<Integer>();
// 首先先将入度为0的点入队列
for(int i=1;i<n+1;i++) {
if(edge[i]==0) {
res[i]=time[i];
queue.offer(i);
}
}
// 进行拓扑排序
while(!queue.isEmpty()) {
int temp=queue.poll();
for(int a:map[temp]) {
// 将入度减一
edge[a]--;
// 如果为edge为0入队列
// 本项杂物所需的时间为前置杂物所需时间加上本身的时间,注意因该为最长的杂物时间加上自己
res[a]=Math.max(res[a],res[temp]+time[a]);
if(edge[a]==0) {
queue.offer(a);
}
}
}
int max=0;
// 遍历一边找出最长的花费时间
for(int i=1;i<=n;i++) {
if(res[i]>max) {
max=res[i];
}
}
// 输出最长的时间
System.out.print(max);
}
}